@naturalcycles/js-lib 14.117.0 → 14.118.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 (62) hide show
  1. package/dist/array/array.util.js +4 -4
  2. package/dist/datetime/dateInterval.js +1 -0
  3. package/dist/datetime/localDate.js +2 -0
  4. package/dist/datetime/localTime.js +2 -3
  5. package/dist/datetime/timeInterval.js +1 -0
  6. package/dist/decorators/asyncMemo.decorator.d.ts +2 -2
  7. package/dist/decorators/createPromiseDecorator.d.ts +6 -6
  8. package/dist/decorators/logMethod.decorator.js +4 -5
  9. package/dist/decorators/memo.decorator.d.ts +2 -2
  10. package/dist/error/tryCatch.d.ts +1 -1
  11. package/dist/http/fetcher.d.ts +146 -0
  12. package/dist/http/fetcher.js +298 -0
  13. package/dist/http/http.model.d.ts +2 -0
  14. package/dist/http/http.model.js +2 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +2 -0
  17. package/dist/object/deepEquals.js +1 -0
  18. package/dist/object/object.util.js +8 -10
  19. package/dist/promise/pRetry.d.ts +1 -1
  20. package/dist/promise/pTimeout.d.ts +1 -1
  21. package/dist/string/pupa.d.ts +2 -2
  22. package/dist/string/readingTime.d.ts +1 -1
  23. package/dist/string/safeJsonStringify.js +5 -7
  24. package/dist/string/stringifyAny.js +2 -1
  25. package/dist/string/url.util.js +1 -1
  26. package/dist-esm/array/array.util.js +4 -4
  27. package/dist-esm/datetime/dateInterval.js +1 -0
  28. package/dist-esm/datetime/localDate.js +2 -0
  29. package/dist-esm/datetime/localTime.js +2 -3
  30. package/dist-esm/datetime/timeInterval.js +1 -0
  31. package/dist-esm/decorators/logMethod.decorator.js +4 -5
  32. package/dist-esm/http/fetcher.js +251 -0
  33. package/dist-esm/http/http.model.js +1 -0
  34. package/dist-esm/index.js +2 -0
  35. package/dist-esm/object/deepEquals.js +1 -0
  36. package/dist-esm/object/object.util.js +8 -10
  37. package/dist-esm/string/safeJsonStringify.js +5 -7
  38. package/dist-esm/string/stringifyAny.js +2 -1
  39. package/dist-esm/string/url.util.js +1 -1
  40. package/package.json +1 -1
  41. package/src/array/array.util.ts +4 -4
  42. package/src/datetime/dateInterval.ts +1 -0
  43. package/src/datetime/localDate.ts +2 -0
  44. package/src/datetime/localTime.ts +2 -2
  45. package/src/datetime/timeInterval.ts +1 -0
  46. package/src/decorators/asyncMemo.decorator.ts +2 -2
  47. package/src/decorators/createPromiseDecorator.ts +4 -4
  48. package/src/decorators/logMethod.decorator.ts +4 -4
  49. package/src/decorators/memo.decorator.ts +2 -2
  50. package/src/error/tryCatch.ts +1 -1
  51. package/src/http/fetcher.ts +469 -0
  52. package/src/http/http.model.ts +3 -0
  53. package/src/index.ts +2 -0
  54. package/src/object/deepEquals.ts +1 -0
  55. package/src/object/object.util.ts +7 -8
  56. package/src/promise/pRetry.ts +1 -1
  57. package/src/promise/pTimeout.ts +1 -1
  58. package/src/string/pupa.ts +1 -1
  59. package/src/string/readingTime.ts +1 -1
  60. package/src/string/safeJsonStringify.ts +3 -5
  61. package/src/string/stringifyAny.ts +3 -1
  62. package/src/string/url.util.ts +1 -1
@@ -160,24 +160,24 @@ function _findLast(items, predicate) {
160
160
  exports._findLast = _findLast;
161
161
  function _takeWhile(items, predicate) {
162
162
  let proceed = true;
163
- return items.filter((v, index) => (proceed = proceed && predicate(v, index)));
163
+ return items.filter((v, index) => (proceed &&= predicate(v, index)));
164
164
  }
165
165
  exports._takeWhile = _takeWhile;
166
166
  function _takeRightWhile(items, predicate) {
167
167
  let proceed = true;
168
- return [...items].reverse().filter((v, index) => (proceed = proceed && predicate(v, index)));
168
+ return [...items].reverse().filter((v, index) => (proceed &&= predicate(v, index)));
169
169
  }
170
170
  exports._takeRightWhile = _takeRightWhile;
171
171
  function _dropWhile(items, predicate) {
172
172
  let proceed = false;
173
- return items.filter((v, index) => (proceed = proceed || !predicate(v, index)));
173
+ return items.filter((v, index) => (proceed ||= !predicate(v, index)));
174
174
  }
175
175
  exports._dropWhile = _dropWhile;
176
176
  function _dropRightWhile(items, predicate) {
177
177
  let proceed = false;
178
178
  return [...items]
179
179
  .reverse()
180
- .filter((v, index) => (proceed = proceed || !predicate(v, index)))
180
+ .filter((v, index) => (proceed ||= !predicate(v, index)))
181
181
  .reverse();
182
182
  }
183
183
  exports._dropRightWhile = _dropRightWhile;
@@ -49,6 +49,7 @@ class DateInterval {
49
49
  */
50
50
  includes(d, incl = '[]') {
51
51
  d = localDate_1.LocalDate.of(d);
52
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
52
53
  return d.isAfter(this.start, incl[0] === '[') && d.isBefore(this.end, incl[1] === ']');
53
54
  }
54
55
  intersects(int, inclusive = true) {
@@ -112,6 +112,7 @@ class LocalDate {
112
112
  const $min = LocalDate.of(min);
113
113
  const $max = LocalDate.of(max).startOf(stepUnit);
114
114
  let current = $min.startOf(stepUnit);
115
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
115
116
  if (current.isAfter($min, incl[0] === '[')) {
116
117
  // ok
117
118
  }
@@ -170,6 +171,7 @@ class LocalDate {
170
171
  }
171
172
  isBetween(min, max, incl = '[)') {
172
173
  let r = this.cmp(min);
174
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
173
175
  if (r < 0 || (r === 0 && incl[0] === '('))
174
176
  return false;
175
177
  r = this.cmp(max);
@@ -347,6 +347,7 @@ class LocalTime {
347
347
  }
348
348
  isBetween(min, max, incl = '[)') {
349
349
  let r = this.cmp(min);
350
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
350
351
  if (r < 0 || (r === 0 && incl[0] === '('))
351
352
  return false;
352
353
  r = this.cmp(max);
@@ -525,9 +526,7 @@ function getWeekYear(date) {
525
526
  else if (date.getTime() >= startOfThisYear.getTime()) {
526
527
  return year;
527
528
  }
528
- else {
529
- return year - 1;
530
- }
529
+ return year - 1;
531
530
  }
532
531
  // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/startOfWeek/index.ts
533
532
  function startOfWeek(date, mutate = false) {
@@ -59,6 +59,7 @@ class TimeInterval {
59
59
  }
60
60
  includes(d, incl = '[)') {
61
61
  d = localTime_1.LocalTime.parseToUnixTimestamp(d);
62
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
62
63
  if (d < this.$start || (d === this.$start && incl[0] === '('))
63
64
  return false;
64
65
  if (d > this.$end || (d === this.$end && incl[1] === ')'))
@@ -6,11 +6,11 @@ export interface AsyncMemoOptions {
6
6
  * Function that creates an instance of `MemoCache`.
7
7
  * e.g LRUMemoCache from `@naturalcycles/nodejs-lib`.
8
8
  */
9
- cacheFactory?: () => AsyncMemoCache;
9
+ cacheFactory?(): AsyncMemoCache;
10
10
  /**
11
11
  * Provide a custom implementation of CacheKey function.
12
12
  */
13
- cacheKeyFn?: (args: any[]) => any;
13
+ cacheKeyFn?(args: any[]): any;
14
14
  /**
15
15
  * Default true.
16
16
  *
@@ -4,15 +4,15 @@ export interface PromiseDecoratorCfg<RES = any, PARAMS = any> {
4
4
  * Called BEFORE the original function.
5
5
  * If Promise is returned - it will be awaited.
6
6
  */
7
- beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void | Promise<void>;
7
+ beforeFn?(r: PromiseDecoratorResp<PARAMS>): void | Promise<void>;
8
8
  /**
9
9
  * Called just AFTER the original function.
10
10
  * The output of this hook will be passed further,
11
11
  * so, pay attention to pass through (or modify) the result.
12
12
  */
13
- thenFn?: (r: PromiseDecoratorResp<PARAMS> & {
13
+ thenFn?(r: PromiseDecoratorResp<PARAMS> & {
14
14
  res: RES;
15
- }) => RES;
15
+ }): RES;
16
16
  /**
17
17
  * Called on Promise.reject.
18
18
  * If `catchFn` is not present - will re-throw the error,
@@ -20,14 +20,14 @@ export interface PromiseDecoratorCfg<RES = any, PARAMS = any> {
20
20
  * If `catchFn` is present - it's responsible for handling or re-throwing the error.
21
21
  * Whatever `catchFn` returns - passed to the original output.
22
22
  */
23
- catchFn?: (r: PromiseDecoratorResp<PARAMS> & {
23
+ catchFn?(r: PromiseDecoratorResp<PARAMS> & {
24
24
  err: any;
25
- }) => RES;
25
+ }): RES;
26
26
  /**
27
27
  * Fires AFTER thenFn / catchFn, like a usual Promise.finally().
28
28
  * Doesn't have access to neither res nor err (same as Promise.finally).
29
29
  */
30
- finallyFn?: (r: PromiseDecoratorResp<PARAMS>) => any;
30
+ finallyFn?(r: PromiseDecoratorResp<PARAMS>): any;
31
31
  }
32
32
  export interface PromiseDecoratorResp<PARAMS> {
33
33
  decoratorParams: PARAMS;
@@ -59,11 +59,9 @@ function _LogMethod(opt = {}) {
59
59
  throw err;
60
60
  });
61
61
  }
62
- else {
63
- // not a Promise
64
- logFinished(logger, callSignature, started, sma, logResultFn, res);
65
- return res;
66
- }
62
+ // not a Promise
63
+ logFinished(logger, callSignature, started, sma, logResultFn, res);
64
+ return res;
67
65
  }
68
66
  catch (err) {
69
67
  logFinished(logger, callSignature, started, sma, logResultFn, undefined, err);
@@ -74,6 +72,7 @@ function _LogMethod(opt = {}) {
74
72
  };
75
73
  }
76
74
  exports._LogMethod = _LogMethod;
75
+ // eslint-disable-next-line max-params
77
76
  function logFinished(logger, callSignature, started, sma, logResultFn, res, err) {
78
77
  const millis = Date.now() - started;
79
78
  const t = ['<<', callSignature, 'took', (0, time_util_1._ms)(millis)];
@@ -6,11 +6,11 @@ export interface MemoOptions {
6
6
  * Function that creates an instance of `MemoCache`.
7
7
  * e.g LRUMemoCache from `@naturalcycles/nodejs-lib`
8
8
  */
9
- cacheFactory?: () => MemoCache;
9
+ cacheFactory?(): MemoCache;
10
10
  /**
11
11
  * Provide a custom implementation of CacheKey function.
12
12
  */
13
- cacheKeyFn?: (args: any[]) => any;
13
+ cacheKeyFn?(args: any[]): any;
14
14
  /**
15
15
  * Defaults to true.
16
16
  * Set to false to skip caching errors.
@@ -5,7 +5,7 @@ export interface TryCatchOptions {
5
5
  * The value returned from the function will be returned from the wrapped method (!).
6
6
  * onError function may be asynchronous.
7
7
  */
8
- onError?: (err: Error) => any;
8
+ onError?(err: Error): any;
9
9
  /**
10
10
  * @default false
11
11
  */
@@ -0,0 +1,146 @@
1
+ /// <reference lib="dom" />
2
+ import { CommonLogger } from '../log/commonLogger';
3
+ import type { Promisable } from '../typeFest';
4
+ import type { HttpMethod, HttpStatusFamily } from './http.model';
5
+ export interface FetcherNormalizedCfg extends FetcherCfg, FetcherNormalizedOptions {
6
+ logger: CommonLogger;
7
+ }
8
+ export interface FetcherCfg {
9
+ baseUrl?: string;
10
+ /**
11
+ * Default rule is that you **are allowed** to mutate req, res, res.retryStatus
12
+ * properties of hook function arguments.
13
+ * If you throw an error from the hook - it will be re-thrown as-is.
14
+ */
15
+ hooks?: {
16
+ /**
17
+ * Allows to mutate req.
18
+ */
19
+ beforeRequest?(req: FetcherRequest): Promisable<void>;
20
+ /**
21
+ * Allows to mutate res.
22
+ * If you set `res.err` - it will be thrown.
23
+ */
24
+ beforeResponse?(res: FetcherResponse): Promisable<void>;
25
+ /**
26
+ * Allows to mutate res.retryStatus to override retry behavior.
27
+ */
28
+ beforeRetry?(res: FetcherResponse): Promisable<void>;
29
+ };
30
+ debug?: boolean;
31
+ logRequest?: boolean;
32
+ logRequestBody?: boolean;
33
+ logResponse?: boolean;
34
+ logResponseBody?: boolean;
35
+ logger?: CommonLogger;
36
+ }
37
+ export interface FetcherRetryStatus {
38
+ retryAttempt: number;
39
+ retryTimeout: number;
40
+ retryStopped: boolean;
41
+ }
42
+ export interface FetcherRetryOptions {
43
+ count: number;
44
+ timeout: number;
45
+ timeoutMax: number;
46
+ timeoutMultiplier: number;
47
+ }
48
+ export interface FetcherNormalizedOptions extends FetcherOptions {
49
+ method: HttpMethod;
50
+ throwHttpErrors: boolean;
51
+ timeoutSeconds: number;
52
+ retry: FetcherRetryOptions;
53
+ retryPost: boolean;
54
+ retry4xx: boolean;
55
+ retry5xx: boolean;
56
+ }
57
+ export interface FetcherOptions {
58
+ method?: HttpMethod;
59
+ throwHttpErrors?: boolean;
60
+ /**
61
+ * Default: 30.
62
+ *
63
+ * Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
64
+ * so both should finish within this single timeout (not each).
65
+ */
66
+ timeoutSeconds?: number;
67
+ json?: any;
68
+ text?: string;
69
+ requestInit?: RequestInit & {
70
+ method?: HttpMethod;
71
+ };
72
+ mode?: FetcherMode;
73
+ /**
74
+ * Default is 2 retries (3 tries in total).
75
+ * Pass `retry: { count: 0 }` to disable retries.
76
+ */
77
+ retry?: Partial<FetcherRetryOptions>;
78
+ /**
79
+ * Defaults to false.
80
+ * Set to true to allow retrying `post` requests.
81
+ */
82
+ retryPost?: boolean;
83
+ /**
84
+ * Defaults to false.
85
+ */
86
+ retry4xx?: boolean;
87
+ /**
88
+ * Defaults to true.
89
+ */
90
+ retry5xx?: boolean;
91
+ }
92
+ export interface FetcherRequest {
93
+ url: string;
94
+ init: RequestInit & {
95
+ method: HttpMethod;
96
+ };
97
+ opt: FetcherNormalizedOptions;
98
+ }
99
+ export interface FetcherSuccessResponse<BODY = unknown> extends FetcherResponse<BODY> {
100
+ err?: undefined;
101
+ fetchResponse: Response;
102
+ body: BODY;
103
+ }
104
+ export interface FetcherErrorResponse<BODY = unknown> extends FetcherResponse<BODY> {
105
+ err: Error;
106
+ }
107
+ export interface FetcherResponse<BODY = unknown> {
108
+ err?: Error;
109
+ req: FetcherRequest;
110
+ fetchResponse?: Response;
111
+ statusFamily?: HttpStatusFamily;
112
+ body?: BODY;
113
+ retryStatus: FetcherRetryStatus;
114
+ }
115
+ export type FetcherMode = 'json' | 'text';
116
+ /**
117
+ * Experimental wrapper around Fetch.
118
+ * Works in both Browser and Node, using `globalThis.fetch`.
119
+ *
120
+ * @experimental
121
+ */
122
+ export declare class Fetcher {
123
+ private constructor();
124
+ cfg: FetcherNormalizedCfg;
125
+ static create(cfg?: FetcherCfg & FetcherOptions): Fetcher;
126
+ getJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
127
+ postJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
128
+ getText(url: string, opt?: FetcherOptions): Promise<string>;
129
+ postText(url: string, opt?: FetcherOptions): Promise<string>;
130
+ fetch<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
131
+ rawFetch<T = unknown>(url: string, rawOpt?: FetcherOptions): Promise<FetcherResponse<T>>;
132
+ private processRetry;
133
+ /**
134
+ * Default is yes,
135
+ * unless there's reason not to (e.g method is POST).
136
+ */
137
+ private shouldRetry;
138
+ private getStatusFamily;
139
+ /**
140
+ * Returns url without baseUrl and before ?queryString
141
+ */
142
+ private getShortUrl;
143
+ private normalizeCfg;
144
+ private normalizeOptions;
145
+ }
146
+ export declare function getFetcher(cfg?: FetcherCfg & FetcherOptions): Fetcher;
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ /// <reference lib="dom"/>
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.getFetcher = exports.Fetcher = void 0;
5
+ const error_util_1 = require("../error/error.util");
6
+ const http_error_1 = require("../error/http.error");
7
+ const number_util_1 = require("../number/number.util");
8
+ const object_util_1 = require("../object/object.util");
9
+ const pDelay_1 = require("../promise/pDelay");
10
+ const json_util_1 = require("../string/json.util");
11
+ const stringifyAny_1 = require("../string/stringifyAny");
12
+ const time_util_1 = require("../time/time.util");
13
+ const types_1 = require("../types");
14
+ const defRetryOptions = {
15
+ count: 2,
16
+ timeout: 500,
17
+ timeoutMax: 30000,
18
+ timeoutMultiplier: 2,
19
+ };
20
+ /**
21
+ * Experimental wrapper around Fetch.
22
+ * Works in both Browser and Node, using `globalThis.fetch`.
23
+ *
24
+ * @experimental
25
+ */
26
+ class Fetcher {
27
+ constructor(cfg = {}) {
28
+ this.cfg = this.normalizeCfg(cfg);
29
+ }
30
+ static create(cfg = {}) {
31
+ return new Fetcher(cfg);
32
+ }
33
+ async getJson(url, opt = {}) {
34
+ return await this.fetch(url, {
35
+ ...opt,
36
+ mode: 'json',
37
+ });
38
+ }
39
+ async postJson(url, opt = {}) {
40
+ return await this.fetch(url, {
41
+ ...opt,
42
+ method: 'post',
43
+ mode: 'json',
44
+ });
45
+ }
46
+ async getText(url, opt = {}) {
47
+ return await this.fetch(url, {
48
+ ...opt,
49
+ mode: 'text',
50
+ });
51
+ }
52
+ async postText(url, opt = {}) {
53
+ return await this.fetch(url, {
54
+ ...opt,
55
+ method: 'post',
56
+ mode: 'text',
57
+ });
58
+ }
59
+ async fetch(url, opt = {}) {
60
+ const res = await this.rawFetch(url, opt);
61
+ if (res.err) {
62
+ if (res.req.opt.throwHttpErrors)
63
+ throw res.err;
64
+ return res;
65
+ }
66
+ return res.body;
67
+ }
68
+ async rawFetch(url, rawOpt = {}) {
69
+ const { baseUrl, logger } = this.cfg;
70
+ const opt = this.normalizeOptions(rawOpt);
71
+ const { method, timeoutSeconds, mode } = opt;
72
+ const req = {
73
+ url,
74
+ init: {
75
+ ...this.cfg.requestInit,
76
+ method,
77
+ },
78
+ opt,
79
+ };
80
+ // setup url
81
+ if (baseUrl) {
82
+ if (url.startsWith('/')) {
83
+ console.warn(`Fetcher: url should not start with / when baseUrl is specified`);
84
+ url = url.slice(1);
85
+ }
86
+ req.url = `${baseUrl}/${url}`;
87
+ }
88
+ // setup request body
89
+ if (opt.json !== undefined) {
90
+ req.init.body = JSON.stringify(opt.json);
91
+ }
92
+ else if (opt.text !== undefined) {
93
+ req.init.body = opt.text;
94
+ }
95
+ // setup timeout
96
+ let timeout;
97
+ if (timeoutSeconds) {
98
+ const abortController = new AbortController();
99
+ req.init.signal = abortController.signal;
100
+ timeout = setTimeout(() => {
101
+ abortController.abort(`timeout of ${timeoutSeconds} sec`);
102
+ }, timeoutSeconds * 1000);
103
+ }
104
+ if (opt.requestInit) {
105
+ (0, types_1._objectAssign)(req.init, opt.requestInit);
106
+ }
107
+ await this.cfg.hooks?.beforeRequest?.(req);
108
+ const res = {
109
+ req,
110
+ retryStatus: {
111
+ retryAttempt: 0,
112
+ retryStopped: false,
113
+ retryTimeout: opt.retry.timeout,
114
+ },
115
+ };
116
+ const shortUrl = this.getShortUrl(req.url);
117
+ const signature = [method.toUpperCase(), shortUrl].join(' ');
118
+ /* eslint-disable no-await-in-loop */
119
+ while (!res.retryStatus.retryStopped) {
120
+ const started = Date.now();
121
+ if (this.cfg.logRequest) {
122
+ const { retryAttempt } = res.retryStatus;
123
+ logger.log([' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${opt.retry.count}`]
124
+ .filter(Boolean)
125
+ .join(' '));
126
+ if (this.cfg.logRequestBody && req.init.body) {
127
+ logger.log(req.init.body); // todo: check if we can _inspect it
128
+ }
129
+ }
130
+ res.fetchResponse = await globalThis.fetch(req.url, req.init);
131
+ res.statusFamily = this.getStatusFamily(res);
132
+ if (res.fetchResponse.ok) {
133
+ if (mode === 'json') {
134
+ // if no body: set responseBody as {}
135
+ // do not throw a "cannot parse null as Json" error
136
+ res.body = res.fetchResponse.body ? await res.fetchResponse.json() : {};
137
+ }
138
+ else if (mode === 'text') {
139
+ res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
140
+ }
141
+ clearTimeout(timeout);
142
+ res.retryStatus.retryStopped = true;
143
+ if (this.cfg.logResponse) {
144
+ const { retryAttempt } = res.retryStatus;
145
+ logger.log([
146
+ ' <<',
147
+ res.fetchResponse.status,
148
+ signature,
149
+ retryAttempt && `try#${retryAttempt + 1}/${opt.retry.count}`,
150
+ (0, time_util_1._since)(started),
151
+ ]
152
+ .filter(Boolean)
153
+ .join(' '));
154
+ if (this.cfg.logResponseBody) {
155
+ logger.log(res.body);
156
+ }
157
+ }
158
+ }
159
+ else {
160
+ clearTimeout(timeout);
161
+ const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
162
+ const errObj = (0, error_util_1._anyToErrorObject)(body);
163
+ const originalMessage = errObj.message;
164
+ errObj.message = [[res.fetchResponse.status, signature].join(' '), originalMessage].join('\n');
165
+ res.err = new http_error_1.HttpError(errObj.message, (0, object_util_1._filterNullishValues)({
166
+ ...errObj.data,
167
+ originalMessage,
168
+ httpStatusCode: res.fetchResponse.status,
169
+ // These properties are provided to be used in e.g custom Sentry error grouping
170
+ // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
171
+ // Enabled, cause `data` is not printed by default when error is HttpError
172
+ // method: req.method,
173
+ url: req.url,
174
+ // tryCount: req.tryCount,
175
+ }));
176
+ if (this.cfg.logResponse) {
177
+ const { retryAttempt } = res.retryStatus;
178
+ logger.error([
179
+ [
180
+ ' <<',
181
+ res.fetchResponse.status,
182
+ signature,
183
+ retryAttempt && `try#${retryAttempt + 1}/${opt.retry.count}`,
184
+ (0, time_util_1._since)(started),
185
+ ]
186
+ .filter(Boolean)
187
+ .join(' '),
188
+ (0, stringifyAny_1._stringifyAny)(body),
189
+ ].join('\n'));
190
+ }
191
+ await this.processRetry(res);
192
+ }
193
+ }
194
+ await this.cfg.hooks?.beforeResponse?.(res);
195
+ return res;
196
+ }
197
+ async processRetry(res) {
198
+ const { retryStatus } = res;
199
+ if (!this.shouldRetry(res)) {
200
+ retryStatus.retryStopped = true;
201
+ }
202
+ await this.cfg.hooks?.beforeRetry?.(res);
203
+ const { count, timeoutMultiplier, timeoutMax } = res.req.opt.retry;
204
+ if (retryStatus.retryAttempt >= count) {
205
+ retryStatus.retryStopped = true;
206
+ }
207
+ if (retryStatus.retryStopped)
208
+ return;
209
+ retryStatus.retryTimeout = (0, number_util_1._clamp)(retryStatus.retryTimeout * timeoutMultiplier, 0, timeoutMax);
210
+ await (0, pDelay_1.pDelay)(retryStatus.retryTimeout);
211
+ }
212
+ /**
213
+ * Default is yes,
214
+ * unless there's reason not to (e.g method is POST).
215
+ */
216
+ shouldRetry(res) {
217
+ const { retryPost, retry4xx, retry5xx } = res.req.opt;
218
+ const { method } = res.req.init;
219
+ if (method === 'post' && !retryPost)
220
+ return false;
221
+ const { statusFamily } = res;
222
+ if (statusFamily === '5xx' && !retry5xx)
223
+ return false;
224
+ if (statusFamily === '4xx' && !retry4xx)
225
+ return false;
226
+ return true; // default is true
227
+ }
228
+ getStatusFamily(res) {
229
+ const status = res.fetchResponse?.status;
230
+ if (!status)
231
+ return;
232
+ if (status >= 500)
233
+ return '5xx';
234
+ if (status >= 400)
235
+ return '4xx';
236
+ if (status >= 300)
237
+ return '3xx';
238
+ if (status >= 200)
239
+ return '2xx';
240
+ if (status >= 100)
241
+ return '1xx';
242
+ }
243
+ /**
244
+ * Returns url without baseUrl and before ?queryString
245
+ */
246
+ getShortUrl(url) {
247
+ const { baseUrl } = this.cfg;
248
+ if (!baseUrl)
249
+ return url;
250
+ return url.split('?')[0].slice(baseUrl.length);
251
+ }
252
+ normalizeCfg(cfg) {
253
+ if (cfg.baseUrl?.endsWith('/')) {
254
+ console.warn(`Fetcher: baseUrl should not end with /`);
255
+ cfg.baseUrl = cfg.baseUrl.slice(0, cfg.baseUrl.length - 1);
256
+ }
257
+ const { debug } = cfg;
258
+ return {
259
+ timeoutSeconds: 30,
260
+ method: 'get',
261
+ throwHttpErrors: true,
262
+ retryPost: false,
263
+ retry4xx: false,
264
+ retry5xx: true,
265
+ logger: console,
266
+ logRequest: debug,
267
+ logRequestBody: debug,
268
+ logResponse: debug,
269
+ logResponseBody: debug,
270
+ ...cfg,
271
+ retry: {
272
+ ...defRetryOptions,
273
+ ...cfg.retry,
274
+ },
275
+ };
276
+ }
277
+ normalizeOptions(opt) {
278
+ const { timeoutSeconds, throwHttpErrors, method, retryPost, retry4xx, retry5xx, retry } = this.cfg;
279
+ return {
280
+ timeoutSeconds,
281
+ throwHttpErrors,
282
+ method,
283
+ retryPost,
284
+ retry4xx,
285
+ retry5xx,
286
+ ...opt,
287
+ retry: {
288
+ ...retry,
289
+ ...(0, object_util_1._filterUndefinedValues)(opt.retry || {}),
290
+ },
291
+ };
292
+ }
293
+ }
294
+ exports.Fetcher = Fetcher;
295
+ function getFetcher(cfg = {}) {
296
+ return Fetcher.create(cfg);
297
+ }
298
+ exports.getFetcher = getFetcher;
@@ -0,0 +1,2 @@
1
+ export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head';
2
+ export type HttpStatusFamily = '5xx' | '4xx' | '3xx' | '2xx' | '1xx';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.d.ts CHANGED
@@ -74,4 +74,6 @@ export * from './datetime/localDate';
74
74
  export * from './datetime/localTime';
75
75
  export * from './datetime/dateInterval';
76
76
  export * from './datetime/timeInterval';
77
+ export * from './http/http.model';
78
+ export * from './http/fetcher';
77
79
  export { is };
package/dist/index.js CHANGED
@@ -79,3 +79,5 @@ tslib_1.__exportStar(require("./datetime/localDate"), exports);
79
79
  tslib_1.__exportStar(require("./datetime/localTime"), exports);
80
80
  tslib_1.__exportStar(require("./datetime/dateInterval"), exports);
81
81
  tslib_1.__exportStar(require("./datetime/timeInterval"), exports);
82
+ tslib_1.__exportStar(require("./http/http.model"), exports);
83
+ tslib_1.__exportStar(require("./http/fetcher"), exports);
@@ -54,6 +54,7 @@ function _deepEquals(a, b) {
54
54
  }
55
55
  return true;
56
56
  }
57
+ // eslint-disable-next-line no-self-compare
57
58
  return a !== a && b !== b;
58
59
  }
59
60
  exports._deepEquals = _deepEquals;