@naturalcycles/js-lib 14.144.1 → 14.146.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 (40) hide show
  1. package/dist/array/array.util.js +2 -2
  2. package/dist/datetime/localDate.js +0 -1
  3. package/dist/datetime/localTime.js +0 -1
  4. package/dist/decorators/createPromiseDecorator.js +0 -1
  5. package/dist/http/fetcher.d.ts +27 -19
  6. package/dist/http/fetcher.js +60 -13
  7. package/dist/http/fetcher.model.d.ts +21 -8
  8. package/dist/object/object.util.js +0 -1
  9. package/dist/object/sortObjectDeep.js +0 -1
  10. package/dist/promise/abortable.d.ts +2 -2
  11. package/dist/promise/pMap.js +1 -1
  12. package/dist/string/leven.js +0 -1
  13. package/dist/string/string.util.js +0 -1
  14. package/dist/vendor/is.d.ts +1 -1
  15. package/dist/vendor/is.js +1 -7
  16. package/dist-esm/array/array.util.js +2 -2
  17. package/dist-esm/datetime/localDate.js +0 -1
  18. package/dist-esm/datetime/localTime.js +0 -1
  19. package/dist-esm/decorators/createPromiseDecorator.js +0 -1
  20. package/dist-esm/http/fetcher.js +61 -71
  21. package/dist-esm/object/object.util.js +0 -1
  22. package/dist-esm/object/sortObjectDeep.js +0 -1
  23. package/dist-esm/promise/pMap.js +15 -34
  24. package/dist-esm/string/leven.js +0 -1
  25. package/dist-esm/string/string.util.js +0 -1
  26. package/dist-esm/vendor/is.js +1 -7
  27. package/package.json +1 -1
  28. package/src/array/array.util.ts +2 -2
  29. package/src/datetime/localDate.ts +0 -2
  30. package/src/datetime/localTime.ts +0 -2
  31. package/src/decorators/createPromiseDecorator.ts +1 -1
  32. package/src/http/fetcher.model.ts +31 -8
  33. package/src/http/fetcher.ts +98 -46
  34. package/src/json-schema/jsonSchema.model.ts +0 -1
  35. package/src/object/object.util.ts +0 -1
  36. package/src/object/sortObjectDeep.ts +0 -1
  37. package/src/promise/pMap.ts +1 -1
  38. package/src/string/leven.ts +1 -1
  39. package/src/string/string.util.ts +0 -2
  40. package/src/vendor/is.ts +7 -10
@@ -15,7 +15,6 @@ import {
15
15
  import { pDelay } from '../promise/pDelay'
16
16
  import { _jsonParse, _jsonParseIfPossible } from '../string/json.util'
17
17
  import { _since } from '../time/time.util'
18
- import { UnixTimestampNumber } from '../types'
19
18
  import type {
20
19
  FetcherAfterResponseHook,
21
20
  FetcherBeforeRequestHook,
@@ -54,7 +53,10 @@ export class Fetcher {
54
53
  const m = method.toLowerCase()
55
54
 
56
55
  // mode=void
57
- ;(this as any)[`${m}Void`] = async (url: string, opt?: FetcherOptions): Promise<void> => {
56
+ ;(this as any)[`${m}Void`] = async (
57
+ url: string,
58
+ opt?: FetcherOptions<void>,
59
+ ): Promise<void> => {
58
60
  return await this.fetch<void>(url, {
59
61
  method,
60
62
  mode: 'void',
@@ -63,7 +65,10 @@ export class Fetcher {
63
65
  }
64
66
 
65
67
  if (method === 'HEAD') return // mode=text
66
- ;(this as any)[`${m}Text`] = async (url: string, opt?: FetcherOptions): Promise<string> => {
68
+ ;(this as any)[`${m}Text`] = async (
69
+ url: string,
70
+ opt?: FetcherOptions<string>,
71
+ ): Promise<string> => {
67
72
  return await this.fetch<string>(url, {
68
73
  method,
69
74
  mode: 'text',
@@ -72,7 +77,7 @@ export class Fetcher {
72
77
  }
73
78
 
74
79
  // Default mode=json, but overridable
75
- ;(this as any)[m] = async <T = unknown>(url: string, opt?: FetcherOptions): Promise<T> => {
80
+ ;(this as any)[m] = async <T = unknown>(url: string, opt?: FetcherOptions<T>): Promise<T> => {
76
81
  return await this.fetch<T>(url, {
77
82
  method,
78
83
  mode: 'json',
@@ -108,28 +113,45 @@ export class Fetcher {
108
113
 
109
114
  // These methods are generated dynamically in the constructor
110
115
  // These default methods use mode=json
111
- get!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
112
- post!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
113
- put!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
114
- patch!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
115
- delete!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
116
+ get!: <T = unknown>(url: string, opt?: FetcherOptions<T>) => Promise<T>
117
+ post!: <T = unknown>(url: string, opt?: FetcherOptions<T>) => Promise<T>
118
+ put!: <T = unknown>(url: string, opt?: FetcherOptions<T>) => Promise<T>
119
+ patch!: <T = unknown>(url: string, opt?: FetcherOptions<T>) => Promise<T>
120
+ delete!: <T = unknown>(url: string, opt?: FetcherOptions<T>) => Promise<T>
116
121
 
117
122
  // mode=text
118
- getText!: (url: string, opt?: FetcherOptions) => Promise<string>
119
- postText!: (url: string, opt?: FetcherOptions) => Promise<string>
120
- putText!: (url: string, opt?: FetcherOptions) => Promise<string>
121
- patchText!: (url: string, opt?: FetcherOptions) => Promise<string>
122
- deleteText!: (url: string, opt?: FetcherOptions) => Promise<string>
123
+ getText!: (url: string, opt?: FetcherOptions<string>) => Promise<string>
124
+ postText!: (url: string, opt?: FetcherOptions<string>) => Promise<string>
125
+ putText!: (url: string, opt?: FetcherOptions<string>) => Promise<string>
126
+ patchText!: (url: string, opt?: FetcherOptions<string>) => Promise<string>
127
+ deleteText!: (url: string, opt?: FetcherOptions<string>) => Promise<string>
123
128
 
124
129
  // mode=void (no body fetching/parsing)
125
- getVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
126
- postVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
127
- putVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
128
- patchVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
129
- deleteVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
130
- headVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
131
-
132
- async fetch<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
130
+ getVoid!: (url: string, opt?: FetcherOptions<void>) => Promise<void>
131
+ postVoid!: (url: string, opt?: FetcherOptions<void>) => Promise<void>
132
+ putVoid!: (url: string, opt?: FetcherOptions<void>) => Promise<void>
133
+ patchVoid!: (url: string, opt?: FetcherOptions<void>) => Promise<void>
134
+ deleteVoid!: (url: string, opt?: FetcherOptions<void>) => Promise<void>
135
+ headVoid!: (url: string, opt?: FetcherOptions<void>) => Promise<void>
136
+
137
+ // mode=readableStream
138
+ /**
139
+ * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
140
+ *
141
+ * More on streams and Node interop:
142
+ * https://css-tricks.com/web-streams-everywhere-and-fetch-for-node-js/
143
+ */
144
+ async getReadableStream(
145
+ url: string,
146
+ opt?: FetcherOptions<ReadableStream<Uint8Array>>,
147
+ ): Promise<ReadableStream<Uint8Array>> {
148
+ return await this.fetch(url, {
149
+ mode: 'readableStream',
150
+ ...opt,
151
+ })
152
+ }
153
+
154
+ async fetch<T = unknown>(url: string, opt?: FetcherOptions<T>): Promise<T> {
133
155
  const res = await this.doFetch<T>(url, opt)
134
156
  if (res.err) {
135
157
  if (res.req.throwHttpErrors) throw res.err
@@ -145,11 +167,14 @@ export class Fetcher {
145
167
  */
146
168
  async doFetch<T = unknown>(
147
169
  url: string,
148
- rawOpt: FetcherOptions = {},
170
+ opt: FetcherOptions<T> = {},
149
171
  ): Promise<FetcherResponse<T>> {
150
- const { logger } = this.cfg
172
+ const req = this.normalizeOptions(url, opt)
173
+ return await this.doFetchRequest<T>(req)
174
+ }
151
175
 
152
- const req = this.normalizeOptions(url, rawOpt)
176
+ async doFetchRequest<T = unknown>(req: FetcherRequest<T>): Promise<FetcherResponse<T>> {
177
+ const { logger } = this.cfg
153
178
  const {
154
179
  timeoutSeconds,
155
180
  init: { method },
@@ -165,7 +190,7 @@ export class Fetcher {
165
190
  }, timeoutSeconds * 1000) as any as number
166
191
  }
167
192
 
168
- for await (const hook of this.cfg.hooks.beforeRequest || []) {
193
+ for (const hook of this.cfg.hooks.beforeRequest || []) {
169
194
  await hook(req)
170
195
  }
171
196
 
@@ -185,7 +210,7 @@ export class Fetcher {
185
210
  } as FetcherResponse<any>
186
211
 
187
212
  while (!res.retryStatus.retryStopped) {
188
- const started = Date.now()
213
+ req.started = Date.now()
189
214
 
190
215
  if (this.cfg.logRequest) {
191
216
  const { retryAttempt } = res.retryStatus
@@ -212,27 +237,29 @@ export class Fetcher {
212
237
  res.statusFamily = this.getStatusFamily(res)
213
238
 
214
239
  if (res.fetchResponse?.ok) {
215
- await this.onOkResponse(
216
- res as FetcherResponse<T> & { fetchResponse: Response },
217
- started,
218
- timeout,
219
- )
240
+ await this.onOkResponse(res as FetcherResponse<T> & { fetchResponse: Response }, timeout)
220
241
  } else {
221
242
  // !res.ok
222
- await this.onNotOkResponse(res, started, timeout)
243
+ await this.onNotOkResponse(res, timeout)
223
244
  }
224
245
  }
225
246
 
226
- for await (const hook of this.cfg.hooks.afterResponse || []) {
247
+ for (const hook of this.cfg.hooks.afterResponse || []) {
227
248
  await hook(res)
228
249
  }
229
250
 
251
+ if (req.paginate && res.ok) {
252
+ const proceed = await req.paginate(req, res)
253
+ if (proceed) {
254
+ return await this.doFetchRequest(req)
255
+ }
256
+ }
257
+
230
258
  return res
231
259
  }
232
260
 
233
261
  private async onOkResponse(
234
262
  res: FetcherResponse<any> & { fetchResponse: Response },
235
- started: UnixTimestampNumber,
236
263
  timeout?: number,
237
264
  ): Promise<void> {
238
265
  const { req } = res
@@ -259,7 +286,7 @@ export class Fetcher {
259
286
  res.err = _anyToError(err)
260
287
  res.ok = false
261
288
 
262
- return await this.onNotOkResponse(res, started, timeout)
289
+ return await this.onNotOkResponse(res, timeout)
263
290
  }
264
291
  } else {
265
292
  // Body had a '' (empty string)
@@ -276,6 +303,14 @@ export class Fetcher {
276
303
  res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {}
277
304
  } else if (mode === 'blob') {
278
305
  res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {}
306
+ } else if (mode === 'readableStream') {
307
+ res.body = res.fetchResponse.body
308
+
309
+ if (res.body === null) {
310
+ res.err = new Error(`fetchResponse.body is null`)
311
+ res.ok = false
312
+ return await this.onNotOkResponse(res, timeout)
313
+ }
279
314
  }
280
315
 
281
316
  clearTimeout(timeout)
@@ -291,7 +326,7 @@ export class Fetcher {
291
326
  res.fetchResponse.status,
292
327
  res.signature,
293
328
  retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
294
- _since(started),
329
+ _since(res.req.started),
295
330
  ]
296
331
  .filter(Boolean)
297
332
  .join(' '),
@@ -310,11 +345,7 @@ export class Fetcher {
310
345
  return await globalThis.fetch(url, init)
311
346
  }
312
347
 
313
- private async onNotOkResponse(
314
- res: FetcherResponse,
315
- started: UnixTimestampNumber,
316
- timeout?: number,
317
- ): Promise<void> {
348
+ private async onNotOkResponse(res: FetcherResponse, timeout?: number): Promise<void> {
318
349
  clearTimeout(timeout)
319
350
 
320
351
  let cause: ErrorObject | undefined
@@ -345,7 +376,7 @@ export class Fetcher {
345
376
  requestBaseUrl: this.cfg.baseUrl || (null as any),
346
377
  requestMethod: res.req.init.method,
347
378
  requestSignature: res.signature,
348
- requestDuration: Date.now() - started,
379
+ requestDuration: Date.now() - res.req.started,
349
380
  }),
350
381
  cause,
351
382
  )
@@ -360,7 +391,7 @@ export class Fetcher {
360
391
  retryStatus.retryStopped = true
361
392
  }
362
393
 
363
- for await (const hook of this.cfg.hooks.beforeRetry || []) {
394
+ for (const hook of this.cfg.hooks.beforeRetry || []) {
364
395
  await hook(res)
365
396
  }
366
397
 
@@ -372,6 +403,26 @@ export class Fetcher {
372
403
 
373
404
  if (retryStatus.retryStopped) return
374
405
 
406
+ // Here we know that more retries will be attempted
407
+ // We don't log "last error", because it will be thrown and logged by consumer,
408
+ // but we should log all previous errors, otherwise they are lost.
409
+ // Here is the right place where we know it's not the "last error"
410
+ if (res.err) {
411
+ const { retryAttempt } = retryStatus
412
+ this.cfg.logger.error(
413
+ [
414
+ ' <<',
415
+ res.fetchResponse?.status || 0,
416
+ res.signature,
417
+ `try#${retryAttempt + 1}/${count + 1}`,
418
+ _since(res.req.started),
419
+ ]
420
+ .filter(Boolean)
421
+ .join(' '),
422
+ res.err.cause || res.err,
423
+ )
424
+ }
425
+
375
426
  retryStatus.retryAttempt++
376
427
  retryStatus.retryTimeout = _clamp(retryStatus.retryTimeout * timeoutMultiplier, 0, timeoutMax)
377
428
 
@@ -477,7 +528,7 @@ export class Fetcher {
477
528
  return norm
478
529
  }
479
530
 
480
- private normalizeOptions(url: string, opt: FetcherOptions): FetcherRequest {
531
+ private normalizeOptions<BODY>(url: string, opt: FetcherOptions<BODY>): FetcherRequest<BODY> {
481
532
  const {
482
533
  timeoutSeconds,
483
534
  throwHttpErrors,
@@ -489,7 +540,8 @@ export class Fetcher {
489
540
  jsonReviver,
490
541
  } = this.cfg
491
542
 
492
- const req: FetcherRequest = {
543
+ const req: FetcherRequest<BODY> = {
544
+ started: Date.now(),
493
545
  mode,
494
546
  url,
495
547
  timeoutSeconds,
@@ -1,6 +1,5 @@
1
1
  import type { AnyObject, StringMap } from '../types'
2
2
 
3
- // eslint-disable-next-line unused-imports/no-unused-vars
4
3
  export type JsonSchema<T = unknown> =
5
4
  | JsonSchemaAny<T>
6
5
  | JsonSchemaOneOf<T>
@@ -139,7 +139,6 @@ export function _mapKeys<T extends AnyObject>(
139
139
  obj: T,
140
140
  mapper: ObjectMapper<T, string>,
141
141
  ): StringMap<T[keyof T]> {
142
- // eslint-disable-next-line unicorn/prefer-object-from-entries
143
142
  return Object.entries(obj).reduce((map, [k, v]) => {
144
143
  map[mapper(k, v, obj) as keyof T] = v
145
144
  return map
@@ -6,7 +6,6 @@ import { _isObject } from '..'
6
6
  export function _sortObjectDeep<T>(o: T): T {
7
7
  // array
8
8
  if (Array.isArray(o)) {
9
- // eslint-disable-next-line unicorn/no-array-callback-reference
10
9
  return o.map(_sortObjectDeep) as any
11
10
  }
12
11
 
@@ -77,7 +77,7 @@ export async function pMap<IN, OUT>(
77
77
  if (concurrency === 1) {
78
78
  // Special case for concurrency == 1
79
79
 
80
- for await (const item of items) {
80
+ for (const item of items) {
81
81
  try {
82
82
  const r = await mapper(item, currentIndex++)
83
83
  if (r === END) break
@@ -70,7 +70,7 @@ export function _leven(first: string, second: string): number {
70
70
  for (index = 0; index < firstLength; index++) {
71
71
  temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1
72
72
  temporary = array[index]!
73
- // eslint-disable-next-line no-multi-assign
73
+
74
74
  result = array[index] =
75
75
  temporary > result
76
76
  ? temporary2 > result
@@ -1,5 +1,3 @@
1
- /* eslint-disable unicorn/prefer-string-slice */
2
-
3
1
  /**
4
2
  * Converts the first character of string to upper case and the remaining to lower case.
5
3
  */
package/src/vendor/is.ts CHANGED
@@ -84,7 +84,6 @@ function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName {
84
84
 
85
85
  export type TypeName = ObjectTypeName | PrimitiveTypeName
86
86
 
87
- // eslint-disable-next-line @typescript-eslint/ban-types
88
87
  function isOfType<T extends Primitive | Function>(type: PrimitiveTypeName | 'function') {
89
88
  return (value: unknown): value is T => typeof value === type
90
89
  }
@@ -168,7 +167,6 @@ is.number = (value: unknown): value is number => isNumberType(value) && !is.nan(
168
167
 
169
168
  is.bigint = isOfType<bigint>('bigint')
170
169
 
171
- // eslint-disable-next-line @typescript-eslint/ban-types
172
170
  is.function_ = isOfType<Function>('function')
173
171
 
174
172
  is.null_ = (value: unknown): value is null => value === null
@@ -228,7 +226,6 @@ is.asyncGeneratorFunction = (value: unknown): value is (...args: any[]) => Promi
228
226
  is.asyncFunction = <T = unknown>(value: unknown): value is (...args: any[]) => Promise<T> =>
229
227
  getObjectType(value) === 'AsyncFunction'
230
228
 
231
- // eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types
232
229
  is.boundFunction = (value: unknown): value is Function =>
233
230
  is.function_(value) && !value.hasOwnProperty('prototype')
234
231
 
@@ -270,7 +267,7 @@ is.urlString = (value: unknown): value is string => {
270
267
  }
271
268
 
272
269
  try {
273
- new URL(value) // eslint-disable-line no-new
270
+ new URL(value)
274
271
  return true
275
272
  } catch {
276
273
  return false
@@ -512,7 +509,7 @@ interface Assert {
512
509
  string: (value: unknown) => asserts value is string
513
510
  number: (value: unknown) => asserts value is number
514
511
  bigint: (value: unknown) => asserts value is bigint
515
- // eslint-disable-next-line @typescript-eslint/ban-types
512
+
516
513
  function_: (value: unknown) => asserts value is Function
517
514
  null_: (value: unknown) => asserts value is null
518
515
  class_: (value: unknown) => asserts value is Class
@@ -536,9 +533,9 @@ interface Assert {
536
533
  promise: <T = unknown>(value: unknown) => asserts value is Promise<T>
537
534
  generatorFunction: (value: unknown) => asserts value is GeneratorFunction
538
535
  asyncGeneratorFunction: (value: unknown) => asserts value is AsyncGeneratorFunction
539
- // eslint-disable-next-line @typescript-eslint/ban-types
536
+
540
537
  asyncFunction: (value: unknown) => asserts value is Function
541
- // eslint-disable-next-line @typescript-eslint/ban-types
538
+
542
539
  boundFunction: (value: unknown) => asserts value is Function
543
540
  regExp: (value: unknown) => asserts value is RegExp
544
541
  date: (value: unknown) => asserts value is Date
@@ -620,7 +617,7 @@ export const assert: Assert = {
620
617
  assertType(is.number(value), 'number', value),
621
618
  bigint: (value: unknown): asserts value is bigint =>
622
619
  assertType(is.bigint(value), 'bigint', value),
623
- // eslint-disable-next-line @typescript-eslint/ban-types
620
+
624
621
  function_: (value: unknown): asserts value is Function =>
625
622
  assertType(is.function_(value), 'Function', value),
626
623
  null_: (value: unknown): asserts value is null => assertType(is.null_(value), 'null', value),
@@ -666,10 +663,10 @@ export const assert: Assert = {
666
663
  assertType(is.generatorFunction(value), 'GeneratorFunction', value),
667
664
  asyncGeneratorFunction: (value: unknown): asserts value is AsyncGeneratorFunction =>
668
665
  assertType(is.asyncGeneratorFunction(value), 'AsyncGeneratorFunction', value),
669
- // eslint-disable-next-line @typescript-eslint/ban-types
666
+
670
667
  asyncFunction: (value: unknown): asserts value is Function =>
671
668
  assertType(is.asyncFunction(value), 'AsyncFunction', value),
672
- // eslint-disable-next-line @typescript-eslint/ban-types
669
+
673
670
  boundFunction: (value: unknown): asserts value is Function =>
674
671
  assertType(is.boundFunction(value), 'Function', value),
675
672
  regExp: (value: unknown): asserts value is RegExp =>