@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
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pRetry = exports.pRetryFn = void 0;
4
4
  const __1 = require("..");
5
- const pTimeout_1 = require("./pTimeout");
6
5
  /**
7
6
  * Returns a Function (!), enhanced with retry capabilities.
8
7
  * Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
@@ -14,8 +13,8 @@ function pRetryFn(fn, opt = {}) {
14
13
  }
15
14
  exports.pRetryFn = pRetryFn;
16
15
  async function pRetry(fn, opt = {}) {
17
- const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, keepStackTrace = true, timeout, } = opt;
18
- const fakeError = keepStackTrace ? new Error('RetryError') : undefined;
16
+ const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, timeout, } = opt;
17
+ const fakeError = timeout ? new Error('TimeoutError') : undefined;
19
18
  let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt;
20
19
  if (opt.logAll) {
21
20
  logSuccess = logFirstAttempt = logRetries = logFailures = true;
@@ -26,70 +25,45 @@ async function pRetry(fn, opt = {}) {
26
25
  const fname = name || fn.name || 'pRetry function';
27
26
  let delay = initialDelay;
28
27
  let attempt = 0;
29
- let timer;
30
- let timedOut = false;
31
- return await new Promise((resolve, reject) => {
32
- const rejectWithTimeout = () => {
33
- timedOut = true; // to prevent more tries
34
- const err = new pTimeout_1.TimeoutError(`"${fname}" timed out after ${timeout} ms`, opt.errorData);
35
- if (fakeError) {
36
- // keep original stack
37
- err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
28
+ /* eslint-disable no-await-in-loop, no-constant-condition */
29
+ while (true) {
30
+ const started = Date.now();
31
+ try {
32
+ attempt++;
33
+ if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
34
+ logger.log(`${fname} attempt #${attempt}...`);
38
35
  }
39
- reject(err);
40
- };
41
- const next = async () => {
42
- if (timedOut)
43
- return;
36
+ let result;
44
37
  if (timeout) {
45
- timer = setTimeout(rejectWithTimeout, timeout);
38
+ await (0, __1.pTimeout)(async () => await fn(attempt), {
39
+ timeout,
40
+ name: fname,
41
+ errorData: opt.errorData,
42
+ fakeError,
43
+ });
46
44
  }
47
- const started = Date.now();
48
- try {
49
- attempt++;
50
- if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
51
- logger.log(`${fname} attempt #${attempt}...`);
52
- }
53
- const r = await fn(attempt);
54
- clearTimeout(timer);
55
- if (logSuccess) {
56
- logger.log(`${fname} attempt #${attempt} succeeded in ${(0, __1._since)(started)}`);
57
- }
58
- resolve(r);
45
+ else {
46
+ result = await fn(attempt);
59
47
  }
60
- catch (err) {
61
- clearTimeout(timer);
62
- if (logFailures) {
63
- logger.warn(`${fname} attempt #${attempt} error in ${(0, __1._since)(started)}:`, (0, __1._stringifyAny)(err, {
64
- includeErrorData: true,
65
- }));
66
- }
67
- if (attempt >= maxAttempts ||
68
- (predicate && !predicate(err, attempt, maxAttempts))) {
69
- // Give up
70
- if (fakeError) {
71
- // Preserve the original call stack
72
- Object.defineProperty(err, 'stack', {
73
- value: err.stack +
74
- '\n --' +
75
- fakeError.stack.replace('Error: RetryError', ''),
76
- });
77
- }
78
- ;
79
- err.data = {
80
- ...err.data,
81
- ...opt.errorData,
82
- };
83
- reject(err);
84
- }
85
- else {
86
- // Retry after delay
87
- delay *= delayMultiplier;
88
- setTimeout(next, delay);
89
- }
48
+ if (logSuccess) {
49
+ logger.log(`${fname} attempt #${attempt} succeeded in ${(0, __1._since)(started)}`);
90
50
  }
91
- };
92
- void next();
93
- });
51
+ return result;
52
+ }
53
+ catch (err) {
54
+ if (logFailures) {
55
+ logger.warn(`${fname} attempt #${attempt} error in ${(0, __1._since)(started)}:`, err);
56
+ }
57
+ if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
58
+ // Give up
59
+ (0, __1._errorDataAppend)(err, opt.errorData);
60
+ throw err;
61
+ }
62
+ // Retry after delay
63
+ delay *= delayMultiplier;
64
+ await (0, __1.pDelay)(delay);
65
+ // back to while(true) loop
66
+ }
67
+ }
94
68
  }
95
69
  exports.pRetry = pRetry;
@@ -23,13 +23,11 @@ export interface PTimeoutOptions {
23
23
  */
24
24
  onTimeout?: (err: TimeoutError) => any;
25
25
  /**
26
- * Defaults to true.
27
- * If true - preserves the stack trace in case of a Timeout (usually - very useful!).
28
- * It has a certain perf cost.
29
- *
30
- * @experimental
26
+ * If passed - fakeError.stack will be used as a stacktrace.
27
+ * This is to "keep stacktrace" when pTimeout is called from another
28
+ * function (like pRetry).
31
29
  */
32
- keepStackTrace?: boolean;
30
+ fakeError?: Error;
33
31
  /**
34
32
  * Will be merged with `err.data` object.
35
33
  */
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pTimeout = exports.pTimeoutFn = exports.TimeoutError = void 0;
4
4
  const app_error_1 = require("../error/app.error");
5
+ const error_util_1 = require("../error/error.util");
5
6
  class TimeoutError extends app_error_1.AppError {
6
7
  constructor(message, data = {}, opt) {
7
8
  super(message, data, opt, 'TimeoutError');
@@ -29,26 +30,23 @@ exports.pTimeoutFn = pTimeoutFn;
29
30
  * If the Function rejects - passes this rejection further.
30
31
  */
31
32
  async function pTimeout(fn, opt) {
32
- const { timeout, name = fn.name || 'pTimeout function', onTimeout, keepStackTrace = true } = opt;
33
- const fakeError = keepStackTrace ? new Error('TimeoutError') : undefined;
33
+ const { timeout, name = fn.name || 'pTimeout function', onTimeout } = opt;
34
+ const fakeError = opt.fakeError || new Error('TimeoutError');
34
35
  // eslint-disable-next-line no-async-promise-executor
35
36
  return await new Promise(async (resolve, reject) => {
36
37
  // Prepare the timeout timer
37
38
  const timer = setTimeout(() => {
38
39
  const err = new TimeoutError(`"${name}" timed out after ${timeout} ms`, opt.errorData);
39
- if (fakeError)
40
- err.stack = fakeError.stack; // keep original stack
40
+ // keep original stack
41
+ err.stack = fakeError.stack.replace('Error: TimeoutError', 'TimeoutError: ' + err.message);
41
42
  if (onTimeout) {
42
43
  try {
43
44
  resolve(onTimeout(err));
44
45
  }
45
46
  catch (err) {
46
- if (fakeError)
47
- err.stack = fakeError.stack; // keep original stack
48
- err.data = {
49
- ...err.data,
50
- ...opt.errorData,
51
- };
47
+ // keep original stack
48
+ err.stack = fakeError.stack.replace('Error: TimeoutError', err.name + ': ' + err.message);
49
+ (0, error_util_1._errorDataAppend)(err, opt.errorData);
52
50
  reject(err);
53
51
  }
54
52
  return;
@@ -20,12 +20,6 @@ export interface StringifyAnyOptions {
20
20
  * Default limit is less than in Node, cause it's likely to be used e.g in Browser alert()
21
21
  */
22
22
  maxLen?: number;
23
- /**
24
- * Pass true to include "stringified" `error.data` in the output.
25
- *
26
- * @default false
27
- */
28
- includeErrorData?: boolean;
29
23
  /**
30
24
  * Set to true to print Error.stack instead of just Error.message.
31
25
  *
@@ -86,11 +86,6 @@ function _stringifyAny(obj, opt = {}) {
86
86
  // `replace` here works ONCE, exactly as we need it
87
87
  s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`);
88
88
  }
89
- // Here we ensure it has `data`
90
- const { data } = obj;
91
- if (opt.includeErrorData && Object.keys(data).length > 0) {
92
- s = [s, _stringifyAny(data, opt)].join('\n');
93
- }
94
89
  }
95
90
  else if (typeof obj.code === 'string') {
96
91
  // Error that has no `data`, but has `code` property
package/dist/types.d.ts CHANGED
@@ -59,6 +59,9 @@ export type UnsavedId<T extends Partial<ObjectWithId>> = Omit<T, 'id'> & {
59
59
  */
60
60
  export type AnyFunction<T = any> = (...args: any[]) => T;
61
61
  export type AnyAsyncFunction<T = any> = (...args: any[]) => Promise<T>;
62
+ export type AsyncFunction<T = any> = () => Promise<T>;
63
+ export type AnyPromisableFunction<T = any> = (...args: any[]) => Promisable<T>;
64
+ export type PromisableFunction<T = any> = () => Promisable<T>;
62
65
  /**
63
66
  * Symbol to indicate END of Sequence.
64
67
  */
@@ -5,9 +5,9 @@
5
5
  /// <reference lib="dom" />
6
6
  import { Class, ObservableLike, Primitive, TypedArray } from '../typeFest';
7
7
  declare const objectTypeNames: readonly ["Function", "Generator", "AsyncGenerator", "GeneratorFunction", "AsyncGeneratorFunction", "AsyncFunction", "Observable", "Array", "Buffer", "Object", "RegExp", "Date", "Error", "Map", "Set", "WeakMap", "WeakSet", "ArrayBuffer", "SharedArrayBuffer", "DataView", "Promise", "URL", "FormData", "URLSearchParams", "HTMLElement", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array"];
8
- type ObjectTypeName = typeof objectTypeNames[number];
8
+ type ObjectTypeName = (typeof objectTypeNames)[number];
9
9
  declare const primitiveTypeNames: readonly ["null", "undefined", "string", "number", "bigint", "boolean", "symbol"];
10
- type PrimitiveTypeName = typeof primitiveTypeNames[number];
10
+ type PrimitiveTypeName = (typeof primitiveTypeNames)[number];
11
11
  export type TypeName = ObjectTypeName | PrimitiveTypeName;
12
12
  export declare function is(value: unknown): TypeName;
13
13
  export declare namespace is {
@@ -76,10 +76,10 @@ function logFinished(logger, callSignature, started, sma, logResultFn, res, err)
76
76
  t.push(`(avg ${_ms(sma.push(millis))})`);
77
77
  }
78
78
  if (err !== undefined) {
79
- t.push('ERROR:', _stringifyAny(err, { includeErrorData: true }));
79
+ t.push('ERROR:', err);
80
80
  }
81
81
  else if (logResultFn) {
82
82
  t.push(...logResultFn(res));
83
83
  }
84
- logger.log(t.filter(Boolean).join(' '));
84
+ logger.log(...t.filter(Boolean));
85
85
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Use it to detect SSR/Node.js environment.
3
+ *
4
+ * Will return `true` in Node.js.
5
+ * Will return `false` in the Browser.
6
+ */
7
+ export function isServerSide() {
8
+ return typeof window === 'undefined';
9
+ }
10
+ /**
11
+ * Use it to detect Browser (not SSR/Node) environment.
12
+ *
13
+ * Will return `true` in the Browser.
14
+ * Will return `false` in Node.js.
15
+ */
16
+ export function isClientSide() {
17
+ return typeof window !== 'undefined';
18
+ }
@@ -143,5 +143,7 @@ export function _isErrorObject(o) {
143
143
  * }
144
144
  */
145
145
  export function _errorDataAppend(err, data) {
146
+ if (!data)
147
+ return;
146
148
  err.data = Object.assign(Object.assign({}, err.data), data);
147
149
  }
@@ -1,4 +1,4 @@
1
- import { _anyToError, _since, _stringifyAny } from '../index';
1
+ import { _anyToError, _since } from '../index';
2
2
  /**
3
3
  * Decorates a function with "try/catch", so it'll never reject/throw.
4
4
  * Only applies to async functions (or, turns sync function into async).
@@ -21,9 +21,7 @@ export function _tryCatch(fn, opt = {}) {
21
21
  }
22
22
  catch (err) {
23
23
  if (logError) {
24
- logger.warn(`tryCatch.${fname} error in ${_since(started)}:\n${_stringifyAny(err, {
25
- includeErrorData: true,
26
- })}`);
24
+ logger.warn(`tryCatch.${fname} error in ${_since(started)}:`, err);
27
25
  }
28
26
  if (onError) {
29
27
  try {
@@ -1,5 +1,6 @@
1
1
  /// <reference lib="dom"/>
2
2
  import { __asyncValues } from "tslib";
3
+ import { isServerSide } from '../env';
3
4
  import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
4
5
  import { HttpError } from '../error/http.error';
5
6
  import { _clamp } from '../number/number.util';
@@ -82,10 +83,10 @@ export class Fetcher {
82
83
  */
83
84
  async rawFetch(url, rawOpt = {}) {
84
85
  var _a, e_1, _b, _c, _d, e_2, _e, _f;
85
- var _g, _h, _j;
86
+ var _g;
86
87
  const { logger } = this.cfg;
87
88
  const req = this.normalizeOptions(url, rawOpt);
88
- const { timeoutSeconds, mode, init: { method }, } = req;
89
+ const { timeoutSeconds, init: { method }, } = req;
89
90
  // setup timeout
90
91
  let timeout;
91
92
  if (timeoutSeconds) {
@@ -96,25 +97,29 @@ export class Fetcher {
96
97
  }, timeoutSeconds * 1000);
97
98
  }
98
99
  try {
99
- for (var _k = true, _l = __asyncValues(this.cfg.hooks.beforeRequest || []), _m; _m = await _l.next(), _a = _m.done, !_a;) {
100
- _c = _m.value;
101
- _k = false;
100
+ for (var _h = true, _j = __asyncValues(this.cfg.hooks.beforeRequest || []), _k; _k = await _j.next(), _a = _k.done, !_a;) {
101
+ _c = _k.value;
102
+ _h = false;
102
103
  try {
103
104
  const hook = _c;
104
105
  await hook(req);
105
106
  }
106
107
  finally {
107
- _k = true;
108
+ _h = true;
108
109
  }
109
110
  }
110
111
  }
111
112
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
112
113
  finally {
113
114
  try {
114
- if (!_k && !_a && (_b = _l.return)) await _b.call(_l);
115
+ if (!_h && !_a && (_b = _j.return)) await _b.call(_j);
115
116
  }
116
117
  finally { if (e_1) throw e_1.error; }
117
118
  }
119
+ const isFullUrl = req.url.includes('://');
120
+ const fullUrl = isFullUrl ? new URL(req.url) : undefined;
121
+ const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url;
122
+ const signature = [method, shortUrl].join(' ');
118
123
  const res = {
119
124
  req,
120
125
  retryStatus: {
@@ -122,10 +127,8 @@ export class Fetcher {
122
127
  retryStopped: false,
123
128
  retryTimeout: req.retry.timeout,
124
129
  },
130
+ signature,
125
131
  };
126
- const fullUrl = new URL(req.url);
127
- const shortUrl = this.getShortUrl(fullUrl);
128
- const signature = [method, shortUrl].join(' ');
129
132
  /* eslint-disable no-await-in-loop */
130
133
  while (!res.retryStatus.retryStopped) {
131
134
  const started = Date.now();
@@ -149,114 +152,124 @@ export class Fetcher {
149
152
  }
150
153
  res.statusFamily = this.getStatusFamily(res);
151
154
  if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
152
- if (mode === 'json') {
153
- if (res.fetchResponse.body) {
154
- const text = await res.fetchResponse.text();
155
- if (text) {
156
- try {
157
- res.body = text;
158
- res.body = JSON.parse(text, req.jsonReviver);
159
- }
160
- catch (err) {
161
- const { message } = _anyToError(err);
162
- res.err = new HttpError([signature, message].join('\n'), {
163
- httpStatusCode: 0,
164
- url: req.url,
165
- });
166
- res.ok = false;
167
- }
168
- }
169
- else {
170
- // Body had a '' (empty string)
171
- res.body = {};
172
- }
173
- }
174
- else {
175
- // if no body: set responseBody as {}
176
- // do not throw a "cannot parse null as Json" error
177
- res.body = {};
178
- }
179
- }
180
- else if (mode === 'text') {
181
- res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
182
- }
183
- else if (mode === 'arrayBuffer') {
184
- res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
185
- }
186
- else if (mode === 'blob') {
187
- res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
188
- }
189
- clearTimeout(timeout);
190
- res.retryStatus.retryStopped = true;
191
- // res.err can happen on JSON.parse error
192
- if (!res.err && this.cfg.logResponse) {
193
- const { retryAttempt } = res.retryStatus;
194
- logger.log([
195
- ' <<',
196
- res.fetchResponse.status,
197
- signature,
198
- retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
199
- _since(started),
200
- ]
201
- .filter(Boolean)
202
- .join(' '));
203
- if (this.cfg.logResponseBody) {
204
- logger.log(res.body);
205
- }
206
- }
155
+ await this.onOkResponse(res, started, timeout);
207
156
  }
208
157
  else {
209
158
  // !res.ok
210
- clearTimeout(timeout);
211
- let errObj;
212
- if (res.fetchResponse) {
213
- const body = _jsonParseIfPossible(await res.fetchResponse.text());
214
- errObj = _anyToErrorObject(body);
215
- }
216
- else if (res.err) {
217
- errObj = _errorToErrorObject(res.err);
218
- }
219
- else {
220
- errObj = {};
221
- }
222
- const originalMessage = errObj.message;
223
- errObj.message = [
224
- [(_h = res.fetchResponse) === null || _h === void 0 ? void 0 : _h.status, signature].filter(Boolean).join(' '),
225
- originalMessage,
226
- ]
227
- .filter(Boolean)
228
- .join('\n');
229
- res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_j = res.fetchResponse) === null || _j === void 0 ? void 0 : _j.status) || 0,
230
- // These properties are provided to be used in e.g custom Sentry error grouping
231
- // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
232
- // Enabled, cause `data` is not printed by default when error is HttpError
233
- // method: req.method,
234
- url: req.url })));
235
- await this.processRetry(res);
159
+ await this.onNotOkResponse(res, timeout);
236
160
  }
237
161
  }
238
162
  try {
239
- for (var _o = true, _p = __asyncValues(this.cfg.hooks.afterResponse || []), _q; _q = await _p.next(), _d = _q.done, !_d;) {
240
- _f = _q.value;
241
- _o = false;
163
+ for (var _l = true, _m = __asyncValues(this.cfg.hooks.afterResponse || []), _o; _o = await _m.next(), _d = _o.done, !_d;) {
164
+ _f = _o.value;
165
+ _l = false;
242
166
  try {
243
167
  const hook = _f;
244
168
  await hook(res);
245
169
  }
246
170
  finally {
247
- _o = true;
171
+ _l = true;
248
172
  }
249
173
  }
250
174
  }
251
175
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
252
176
  finally {
253
177
  try {
254
- if (!_o && !_d && (_e = _p.return)) await _e.call(_p);
178
+ if (!_l && !_d && (_e = _m.return)) await _e.call(_m);
255
179
  }
256
180
  finally { if (e_2) throw e_2.error; }
257
181
  }
258
182
  return res;
259
183
  }
184
+ async onOkResponse(res, started, timeout) {
185
+ const { req } = res;
186
+ const { mode } = res.req;
187
+ if (mode === 'json') {
188
+ if (res.fetchResponse.body) {
189
+ const text = await res.fetchResponse.text();
190
+ if (text) {
191
+ try {
192
+ res.body = text;
193
+ res.body = JSON.parse(text, req.jsonReviver);
194
+ }
195
+ catch (err) {
196
+ const { message } = _anyToError(err);
197
+ res.err = new HttpError([res.signature, message].join('\n'), {
198
+ httpStatusCode: 0,
199
+ url: req.url,
200
+ });
201
+ res.ok = false;
202
+ }
203
+ }
204
+ else {
205
+ // Body had a '' (empty string)
206
+ res.body = {};
207
+ }
208
+ }
209
+ else {
210
+ // if no body: set responseBody as {}
211
+ // do not throw a "cannot parse null as Json" error
212
+ res.body = {};
213
+ }
214
+ }
215
+ else if (mode === 'text') {
216
+ res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
217
+ }
218
+ else if (mode === 'arrayBuffer') {
219
+ res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
220
+ }
221
+ else if (mode === 'blob') {
222
+ res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
223
+ }
224
+ clearTimeout(timeout);
225
+ res.retryStatus.retryStopped = true;
226
+ // res.err can happen on JSON.parse error
227
+ if (!res.err && this.cfg.logResponse) {
228
+ const { retryAttempt } = res.retryStatus;
229
+ const { logger } = this.cfg;
230
+ logger.log([
231
+ ' <<',
232
+ res.fetchResponse.status,
233
+ res.signature,
234
+ retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
235
+ _since(started),
236
+ ]
237
+ .filter(Boolean)
238
+ .join(' '));
239
+ if (this.cfg.logResponseBody) {
240
+ logger.log(res.body);
241
+ }
242
+ }
243
+ }
244
+ async onNotOkResponse(res, timeout) {
245
+ var _a, _b;
246
+ clearTimeout(timeout);
247
+ let errObj;
248
+ if (res.fetchResponse) {
249
+ const body = _jsonParseIfPossible(await res.fetchResponse.text());
250
+ errObj = _anyToErrorObject(body);
251
+ }
252
+ else if (res.err) {
253
+ errObj = _errorToErrorObject(res.err);
254
+ }
255
+ else {
256
+ errObj = {};
257
+ }
258
+ const originalMessage = errObj.message;
259
+ errObj.message = [
260
+ [(_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status, res.signature].filter(Boolean).join(' '),
261
+ originalMessage,
262
+ ]
263
+ .filter(Boolean)
264
+ .join('\n');
265
+ res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_b = res.fetchResponse) === null || _b === void 0 ? void 0 : _b.status) || 0,
266
+ // These properties are provided to be used in e.g custom Sentry error grouping
267
+ // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
268
+ // Enabled, cause `data` is not printed by default when error is HttpError
269
+ // method: req.method,
270
+ url: res.req.url })));
271
+ await this.processRetry(res);
272
+ }
260
273
  async processRetry(res) {
261
274
  var _a, e_3, _b, _c;
262
275
  const { retryStatus } = res;
@@ -347,7 +360,7 @@ export class Fetcher {
347
360
  if (!this.cfg.logWithSearchParams) {
348
361
  shortUrl = shortUrl.split('?')[0];
349
362
  }
350
- if (!this.cfg.logWithPrefixUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
363
+ if (!this.cfg.logWithBaseUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
351
364
  shortUrl = shortUrl.slice(baseUrl.length);
352
365
  }
353
366
  return shortUrl;
@@ -375,7 +388,7 @@ export class Fetcher {
375
388
  logRequestBody: debug,
376
389
  logResponse: debug,
377
390
  logResponseBody: debug,
378
- logWithPrefixUrl: true,
391
+ logWithBaseUrl: isServerSide(),
379
392
  logWithSearchParams: true,
380
393
  retry: Object.assign({}, defRetryOptions),
381
394
  init: {
package/dist-esm/index.js CHANGED
@@ -61,6 +61,7 @@ export * from './unit/size.util';
61
61
  export * from './log/commonLogger';
62
62
  export * from './string/safeJsonStringify';
63
63
  export * from './promise/pQueue';
64
+ export * from './promise/abortable';
64
65
  export * from './seq/seq';
65
66
  export * from './math/stack.util';
66
67
  export * from './string/leven';
@@ -72,6 +73,7 @@ export * from './datetime/localDate';
72
73
  export * from './datetime/localTime';
73
74
  export * from './datetime/dateInterval';
74
75
  export * from './datetime/timeInterval';
76
+ export * from './env';
75
77
  export * from './http/http.model';
76
78
  export * from './http/fetcher';
77
79
  export * from './http/fetcher.model';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Similar to AbortController and AbortSignal.
3
+ * Similar to pDefer and Promise.
4
+ * Similar to Subject and Observable.
5
+ *
6
+ * Minimal interface for something that can be aborted in the future,
7
+ * but not necessary.
8
+ * Allows to listen to `onAbort` event.
9
+ *
10
+ * @experimental
11
+ */
12
+ export class Abortable {
13
+ constructor(onAbort) {
14
+ this.onAbort = onAbort;
15
+ this.aborted = false;
16
+ }
17
+ abort() {
18
+ var _a;
19
+ if (this.aborted)
20
+ return;
21
+ this.aborted = true;
22
+ (_a = this.onAbort) === null || _a === void 0 ? void 0 : _a.call(this);
23
+ this.onAbort = undefined; // cleanup listener
24
+ }
25
+ clear() {
26
+ this.onAbort = undefined;
27
+ }
28
+ }
29
+ // convenience function
30
+ export function abortable(onAbort) {
31
+ return new Abortable(onAbort);
32
+ }
@@ -11,5 +11,7 @@ export function pDefer() {
11
11
  });
12
12
  promise.resolve = resolve;
13
13
  promise.reject = reject;
14
+ promise.rejectAborted = reason => reject(new Error(['Aborted', reason].filter(Boolean).join(': ')));
15
+ promise.abort = reason => promise.rejectAborted(reason);
14
16
  return promise;
15
17
  }