@naturalcycles/js-lib 14.140.0 → 14.141.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.
@@ -159,3 +159,7 @@ export declare function _maxOrUndefined<T>(array: T[]): NonNullable<T> | undefin
159
159
  * Filters out nullish values (undefined and null).
160
160
  */
161
161
  export declare function _max<T>(array: T[]): NonNullable<T>;
162
+ export declare function _maxBy<T>(array: T[], mapper: Mapper<T, number | undefined>): T;
163
+ export declare function _minBy<T>(array: T[], mapper: Mapper<T, number | undefined>): T;
164
+ export declare function _maxByOrUndefined<T>(array: T[], mapper: Mapper<T, number | undefined>): T | undefined;
165
+ export declare function _minByOrUndefined<T>(array: T[], mapper: Mapper<T, number | undefined>): T | undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._max = exports._maxOrUndefined = exports._min = exports._minOrUndefined = exports._lastOrUndefined = exports._last = exports._shuffle = exports._mapToObject = exports._sumBy = exports._sum = exports._difference = exports._intersection = exports._countBy = exports._dropRightWhile = exports._dropWhile = exports._takeRightWhile = exports._takeWhile = exports._findLast = exports._sortBy = exports._groupBy = exports._by = exports._uniqBy = exports._uniq = exports._flattenDeep = exports._flatten = exports._chunk = void 0;
3
+ exports._minByOrUndefined = exports._maxByOrUndefined = exports._minBy = exports._maxBy = exports._max = exports._maxOrUndefined = exports._min = exports._minOrUndefined = exports._lastOrUndefined = exports._last = exports._shuffle = exports._mapToObject = exports._sumBy = exports._sum = exports._difference = exports._intersection = exports._countBy = exports._dropRightWhile = exports._dropWhile = exports._takeRightWhile = exports._takeWhile = exports._findLast = exports._sortBy = exports._groupBy = exports._by = exports._uniqBy = exports._uniq = exports._flattenDeep = exports._flatten = exports._chunk = void 0;
4
4
  const is_util_1 = require("../is.util");
5
5
  /**
6
6
  * Creates an array of elements split into groups the length of size. If collection can’t be split evenly, the
@@ -314,3 +314,48 @@ function _max(array) {
314
314
  return a.reduce((max, item) => (max >= item ? max : item));
315
315
  }
316
316
  exports._max = _max;
317
+ function _maxBy(array, mapper) {
318
+ const max = _maxByOrUndefined(array, mapper);
319
+ if (max === undefined)
320
+ throw new Error(`_maxBy returned undefined`);
321
+ return max;
322
+ }
323
+ exports._maxBy = _maxBy;
324
+ function _minBy(array, mapper) {
325
+ const min = _minByOrUndefined(array, mapper);
326
+ if (min === undefined)
327
+ throw new Error(`_minBy returned undefined`);
328
+ return min;
329
+ }
330
+ exports._minBy = _minBy;
331
+ // todo: looks like it _maxByOrUndefined/_minByOrUndefined can be DRYer
332
+ function _maxByOrUndefined(array, mapper) {
333
+ if (!array.length)
334
+ return;
335
+ let maxItem;
336
+ let max;
337
+ array.forEach((item, i) => {
338
+ const v = mapper(item, i);
339
+ if (v !== undefined && (max === undefined || v > max)) {
340
+ maxItem = item;
341
+ max = v;
342
+ }
343
+ });
344
+ return maxItem;
345
+ }
346
+ exports._maxByOrUndefined = _maxByOrUndefined;
347
+ function _minByOrUndefined(array, mapper) {
348
+ if (!array.length)
349
+ return;
350
+ let minItem;
351
+ let min;
352
+ array.forEach((item, i) => {
353
+ const v = mapper(item, i);
354
+ if (v !== undefined && (min === undefined || v < min)) {
355
+ minItem = item;
356
+ min = v;
357
+ }
358
+ });
359
+ return minItem;
360
+ }
361
+ exports._minByOrUndefined = _minByOrUndefined;
@@ -34,9 +34,14 @@ export declare class Fetcher {
34
34
  /**
35
35
  * Returns FetcherResponse.
36
36
  * Never throws, returns `err` property in the response instead.
37
+ * Use this method instead of `throwHttpErrors: false` or try-catching.
37
38
  */
38
- rawFetch<T = unknown>(url: string, rawOpt?: FetcherOptions): Promise<FetcherResponse<T>>;
39
+ doFetch<T = unknown>(url: string, rawOpt?: FetcherOptions): Promise<FetcherResponse<T>>;
39
40
  private onOkResponse;
41
+ /**
42
+ * This method exists to be able to easily mock it.
43
+ */
44
+ callNativeFetch(url: string, init: RequestInit): Promise<Response>;
40
45
  private onNotOkResponse;
41
46
  private processRetry;
42
47
  /**
@@ -78,7 +78,7 @@ class Fetcher {
78
78
  return new Fetcher(cfg);
79
79
  }
80
80
  async fetch(url, opt) {
81
- const res = await this.rawFetch(url, opt);
81
+ const res = await this.doFetch(url, opt);
82
82
  if (res.err) {
83
83
  if (res.req.throwHttpErrors)
84
84
  throw res.err;
@@ -89,8 +89,9 @@ class Fetcher {
89
89
  /**
90
90
  * Returns FetcherResponse.
91
91
  * Never throws, returns `err` property in the response instead.
92
+ * Use this method instead of `throwHttpErrors: false` or try-catching.
92
93
  */
93
- async rawFetch(url, rawOpt = {}) {
94
+ async doFetch(url, rawOpt = {}) {
94
95
  const { logger } = this.cfg;
95
96
  const req = this.normalizeOptions(url, rawOpt);
96
97
  const { timeoutSeconds, init: { method }, } = req;
@@ -131,7 +132,7 @@ class Fetcher {
131
132
  }
132
133
  }
133
134
  try {
134
- res.fetchResponse = await globalThis.fetch(req.url, req.init);
135
+ res.fetchResponse = await this.callNativeFetch(req.url, req.init);
135
136
  res.ok = res.fetchResponse.ok;
136
137
  }
137
138
  catch (err) {
@@ -215,6 +216,12 @@ class Fetcher {
215
216
  }
216
217
  }
217
218
  }
219
+ /**
220
+ * This method exists to be able to easily mock it.
221
+ */
222
+ async callNativeFetch(url, init) {
223
+ return await globalThis.fetch(url, init);
224
+ }
218
225
  async onNotOkResponse(res, timeout) {
219
226
  clearTimeout(timeout);
220
227
  let errObj;
@@ -69,7 +69,15 @@ function _stringifyAny(obj, opt = {}) {
69
69
  // if (obj?.name === 'Error') {
70
70
  // s = obj.message
71
71
  // }
72
- s = [obj.name, obj.message].filter(Boolean).join(': ');
72
+ if ((0, error_util_1._isErrorObject)(obj) && (0, error_util_1._isHttpErrorObject)(obj)) {
73
+ // Printing (0) to avoid ambiguity
74
+ s = `${obj.name}(${obj.data.httpStatusCode}): ${obj.message}`;
75
+ }
76
+ s ||= [obj.name, obj.message].filter(Boolean).join(': ');
77
+ if (typeof obj.code === 'string') {
78
+ // Error that has no `data`, but has `code` property
79
+ s += `\ncode: ${obj.code}`;
80
+ }
73
81
  if (opt.includeErrorStack && obj.stack) {
74
82
  // Here we're using the previously-generated "title line" (e.g "Error: some_message"),
75
83
  // concatenating it with the Stack (but without the title line of the Stack)
@@ -79,18 +87,6 @@ function _stringifyAny(obj, opt = {}) {
79
87
  const sLines = s.split('\n').length;
80
88
  s = [s, ...obj.stack.split('\n').slice(sLines)].join('\n');
81
89
  }
82
- if ((0, error_util_1._isErrorObject)(obj)) {
83
- if ((0, error_util_1._isHttpErrorObject)(obj)) {
84
- // Only include (statusCode) if it's non-zero
85
- // No: print (0), as it removes ambiguity
86
- // `replace` here works ONCE, exactly as we need it
87
- s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`);
88
- }
89
- }
90
- else if (typeof obj.code === 'string') {
91
- // Error that has no `data`, but has `code` property
92
- s = [s, `code: ${obj.code}`].join('\n');
93
- }
94
90
  if (supportsAggregateError && obj instanceof AggregateError && obj.errors.length) {
95
91
  s = [
96
92
  s,
@@ -99,7 +95,7 @@ function _stringifyAny(obj, opt = {}) {
99
95
  ].join('\n');
100
96
  }
101
97
  if (obj.cause && includeErrorCause) {
102
- s = s + '\ncaused by: ' + _stringifyAny(obj.cause, opt);
98
+ s = s + '\nCaused by: ' + _stringifyAny(obj.cause, opt);
103
99
  }
104
100
  }
105
101
  else if (typeof obj === 'string') {
@@ -285,3 +285,44 @@ export function _max(array) {
285
285
  throw new Error('_max called on empty array');
286
286
  return a.reduce((max, item) => (max >= item ? max : item));
287
287
  }
288
+ export function _maxBy(array, mapper) {
289
+ const max = _maxByOrUndefined(array, mapper);
290
+ if (max === undefined)
291
+ throw new Error(`_maxBy returned undefined`);
292
+ return max;
293
+ }
294
+ export function _minBy(array, mapper) {
295
+ const min = _minByOrUndefined(array, mapper);
296
+ if (min === undefined)
297
+ throw new Error(`_minBy returned undefined`);
298
+ return min;
299
+ }
300
+ // todo: looks like it _maxByOrUndefined/_minByOrUndefined can be DRYer
301
+ export function _maxByOrUndefined(array, mapper) {
302
+ if (!array.length)
303
+ return;
304
+ let maxItem;
305
+ let max;
306
+ array.forEach((item, i) => {
307
+ const v = mapper(item, i);
308
+ if (v !== undefined && (max === undefined || v > max)) {
309
+ maxItem = item;
310
+ max = v;
311
+ }
312
+ });
313
+ return maxItem;
314
+ }
315
+ export function _minByOrUndefined(array, mapper) {
316
+ if (!array.length)
317
+ return;
318
+ let minItem;
319
+ let min;
320
+ array.forEach((item, i) => {
321
+ const v = mapper(item, i);
322
+ if (v !== undefined && (min === undefined || v < min)) {
323
+ minItem = item;
324
+ min = v;
325
+ }
326
+ });
327
+ return minItem;
328
+ }
@@ -67,7 +67,7 @@ export class Fetcher {
67
67
  return new Fetcher(cfg);
68
68
  }
69
69
  async fetch(url, opt) {
70
- const res = await this.rawFetch(url, opt);
70
+ const res = await this.doFetch(url, opt);
71
71
  if (res.err) {
72
72
  if (res.req.throwHttpErrors)
73
73
  throw res.err;
@@ -78,8 +78,9 @@ export class Fetcher {
78
78
  /**
79
79
  * Returns FetcherResponse.
80
80
  * Never throws, returns `err` property in the response instead.
81
+ * Use this method instead of `throwHttpErrors: false` or try-catching.
81
82
  */
82
- async rawFetch(url, rawOpt = {}) {
83
+ async doFetch(url, rawOpt = {}) {
83
84
  var _a, e_1, _b, _c, _d, e_2, _e, _f;
84
85
  var _g;
85
86
  const { logger } = this.cfg;
@@ -139,7 +140,7 @@ export class Fetcher {
139
140
  }
140
141
  }
141
142
  try {
142
- res.fetchResponse = await globalThis.fetch(req.url, req.init);
143
+ res.fetchResponse = await this.callNativeFetch(req.url, req.init);
143
144
  res.ok = res.fetchResponse.ok;
144
145
  }
145
146
  catch (err) {
@@ -240,6 +241,12 @@ export class Fetcher {
240
241
  }
241
242
  }
242
243
  }
244
+ /**
245
+ * This method exists to be able to easily mock it.
246
+ */
247
+ async callNativeFetch(url, init) {
248
+ return await globalThis.fetch(url, init);
249
+ }
243
250
  async onNotOkResponse(res, timeout) {
244
251
  var _a, _b;
245
252
  clearTimeout(timeout);
@@ -65,7 +65,15 @@ export function _stringifyAny(obj, opt = {}) {
65
65
  // if (obj?.name === 'Error') {
66
66
  // s = obj.message
67
67
  // }
68
- s = [obj.name, obj.message].filter(Boolean).join(': ');
68
+ if (_isErrorObject(obj) && _isHttpErrorObject(obj)) {
69
+ // Printing (0) to avoid ambiguity
70
+ s = `${obj.name}(${obj.data.httpStatusCode}): ${obj.message}`;
71
+ }
72
+ s || (s = [obj.name, obj.message].filter(Boolean).join(': '));
73
+ if (typeof obj.code === 'string') {
74
+ // Error that has no `data`, but has `code` property
75
+ s += `\ncode: ${obj.code}`;
76
+ }
69
77
  if (opt.includeErrorStack && obj.stack) {
70
78
  // Here we're using the previously-generated "title line" (e.g "Error: some_message"),
71
79
  // concatenating it with the Stack (but without the title line of the Stack)
@@ -75,18 +83,6 @@ export function _stringifyAny(obj, opt = {}) {
75
83
  const sLines = s.split('\n').length;
76
84
  s = [s, ...obj.stack.split('\n').slice(sLines)].join('\n');
77
85
  }
78
- if (_isErrorObject(obj)) {
79
- if (_isHttpErrorObject(obj)) {
80
- // Only include (statusCode) if it's non-zero
81
- // No: print (0), as it removes ambiguity
82
- // `replace` here works ONCE, exactly as we need it
83
- s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`);
84
- }
85
- }
86
- else if (typeof obj.code === 'string') {
87
- // Error that has no `data`, but has `code` property
88
- s = [s, `code: ${obj.code}`].join('\n');
89
- }
90
86
  if (supportsAggregateError && obj instanceof AggregateError && obj.errors.length) {
91
87
  s = [
92
88
  s,
@@ -95,7 +91,7 @@ export function _stringifyAny(obj, opt = {}) {
95
91
  ].join('\n');
96
92
  }
97
93
  if (obj.cause && includeErrorCause) {
98
- s = s + '\ncaused by: ' + _stringifyAny(obj.cause, opt);
94
+ s = s + '\nCaused by: ' + _stringifyAny(obj.cause, opt);
99
95
  }
100
96
  }
101
97
  else if (typeof obj === 'string') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.140.0",
3
+ "version": "14.141.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -321,3 +321,53 @@ export function _max<T>(array: T[]): NonNullable<T> {
321
321
  if (!a.length) throw new Error('_max called on empty array')
322
322
  return a.reduce((max, item) => (max >= item ? max : item))
323
323
  }
324
+
325
+ export function _maxBy<T>(array: T[], mapper: Mapper<T, number | undefined>): T {
326
+ const max = _maxByOrUndefined(array, mapper)
327
+ if (max === undefined) throw new Error(`_maxBy returned undefined`)
328
+ return max
329
+ }
330
+
331
+ export function _minBy<T>(array: T[], mapper: Mapper<T, number | undefined>): T {
332
+ const min = _minByOrUndefined(array, mapper)
333
+ if (min === undefined) throw new Error(`_minBy returned undefined`)
334
+ return min
335
+ }
336
+
337
+ // todo: looks like it _maxByOrUndefined/_minByOrUndefined can be DRYer
338
+
339
+ export function _maxByOrUndefined<T>(
340
+ array: T[],
341
+ mapper: Mapper<T, number | undefined>,
342
+ ): T | undefined {
343
+ if (!array.length) return
344
+ let maxItem: T | undefined
345
+ let max: number | undefined
346
+ array.forEach((item, i) => {
347
+ const v = mapper(item, i)
348
+ if (v !== undefined && (max === undefined || v > max)) {
349
+ maxItem = item
350
+ max = v
351
+ }
352
+ })
353
+
354
+ return maxItem
355
+ }
356
+
357
+ export function _minByOrUndefined<T>(
358
+ array: T[],
359
+ mapper: Mapper<T, number | undefined>,
360
+ ): T | undefined {
361
+ if (!array.length) return
362
+ let minItem: T | undefined
363
+ let min: number | undefined
364
+ array.forEach((item, i) => {
365
+ const v = mapper(item, i)
366
+ if (v !== undefined && (min === undefined || v < min)) {
367
+ minItem = item
368
+ min = v
369
+ }
370
+ })
371
+
372
+ return minItem
373
+ }
@@ -129,7 +129,7 @@ export class Fetcher {
129
129
  headVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
130
130
 
131
131
  async fetch<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
132
- const res = await this.rawFetch<T>(url, opt)
132
+ const res = await this.doFetch<T>(url, opt)
133
133
  if (res.err) {
134
134
  if (res.req.throwHttpErrors) throw res.err
135
135
  return res as any
@@ -140,8 +140,9 @@ export class Fetcher {
140
140
  /**
141
141
  * Returns FetcherResponse.
142
142
  * Never throws, returns `err` property in the response instead.
143
+ * Use this method instead of `throwHttpErrors: false` or try-catching.
143
144
  */
144
- async rawFetch<T = unknown>(
145
+ async doFetch<T = unknown>(
145
146
  url: string,
146
147
  rawOpt: FetcherOptions = {},
147
148
  ): Promise<FetcherResponse<T>> {
@@ -198,7 +199,7 @@ export class Fetcher {
198
199
  }
199
200
 
200
201
  try {
201
- res.fetchResponse = await globalThis.fetch(req.url, req.init)
202
+ res.fetchResponse = await this.callNativeFetch(req.url, req.init)
202
203
  res.ok = res.fetchResponse.ok
203
204
  } catch (err) {
204
205
  // For example, CORS error would result in "TypeError: failed to fetch" here
@@ -294,6 +295,13 @@ export class Fetcher {
294
295
  }
295
296
  }
296
297
 
298
+ /**
299
+ * This method exists to be able to easily mock it.
300
+ */
301
+ async callNativeFetch(url: string, init: RequestInit): Promise<Response> {
302
+ return await globalThis.fetch(url, init)
303
+ }
304
+
297
305
  private async onNotOkResponse(res: FetcherResponse, timeout?: number): Promise<void> {
298
306
  clearTimeout(timeout)
299
307
 
@@ -103,7 +103,17 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
103
103
  // if (obj?.name === 'Error') {
104
104
  // s = obj.message
105
105
  // }
106
- s = [obj.name, obj.message].filter(Boolean).join(': ')
106
+ if (_isErrorObject(obj) && _isHttpErrorObject(obj)) {
107
+ // Printing (0) to avoid ambiguity
108
+ s = `${obj.name}(${obj.data.httpStatusCode}): ${obj.message}`
109
+ }
110
+
111
+ s ||= [obj.name, obj.message].filter(Boolean).join(': ')
112
+
113
+ if (typeof (obj as any).code === 'string') {
114
+ // Error that has no `data`, but has `code` property
115
+ s += `\ncode: ${(obj as any).code}`
116
+ }
107
117
 
108
118
  if (opt.includeErrorStack && obj.stack) {
109
119
  // Here we're using the previously-generated "title line" (e.g "Error: some_message"),
@@ -116,18 +126,6 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
116
126
  s = [s, ...obj.stack.split('\n').slice(sLines)].join('\n')
117
127
  }
118
128
 
119
- if (_isErrorObject(obj)) {
120
- if (_isHttpErrorObject(obj)) {
121
- // Only include (statusCode) if it's non-zero
122
- // No: print (0), as it removes ambiguity
123
- // `replace` here works ONCE, exactly as we need it
124
- s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`)
125
- }
126
- } else if (typeof (obj as any).code === 'string') {
127
- // Error that has no `data`, but has `code` property
128
- s = [s, `code: ${(obj as any).code}`].join('\n')
129
- }
130
-
131
129
  if (supportsAggregateError && obj instanceof AggregateError && obj.errors.length) {
132
130
  s = [
133
131
  s,
@@ -137,7 +135,7 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
137
135
  }
138
136
 
139
137
  if (obj.cause && includeErrorCause) {
140
- s = s + '\ncaused by: ' + _stringifyAny(obj.cause, opt)
138
+ s = s + '\nCaused by: ' + _stringifyAny(obj.cause, opt)
141
139
  }
142
140
  } else if (typeof obj === 'string') {
143
141
  //