@naturalcycles/js-lib 14.150.0 → 14.151.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.
@@ -87,6 +87,17 @@ export interface HttpRequestErrorData extends ErrorData {
87
87
  */
88
88
  responseStatusCode: HttpStatusCode;
89
89
  }
90
+ /**
91
+ * Sometimes there are cases when Errors come from unexpected places,
92
+ * where err is not instanceof Error, but still looks like Error.
93
+ */
94
+ export interface ErrorLike {
95
+ name: string;
96
+ message: string;
97
+ stack?: string;
98
+ data?: any;
99
+ cause?: any;
100
+ }
90
101
  /**
91
102
  * Portable object that represents Error.
92
103
  * Has extendable generic `data` property.
@@ -1,4 +1,4 @@
1
- import type { ErrorData, ErrorObject, BackendErrorResponseObject, Class, HttpRequestErrorData } from '..';
1
+ import type { ErrorData, ErrorObject, BackendErrorResponseObject, Class, HttpRequestErrorData, ErrorLike } from '..';
2
2
  import { AppError } from '..';
3
3
  /**
4
4
  * Useful to ensure that error in `catch (err) { ... }`
@@ -16,7 +16,7 @@ export declare function _anyToError<ERROR_TYPE extends Error = Error>(o: any, er
16
16
  * Objects (not Errors) get converted to prettified JSON string (via `_stringifyAny`).
17
17
  */
18
18
  export declare function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(o: any, errorData?: Partial<DATA_TYPE>): ErrorObject<DATA_TYPE>;
19
- export declare function _errorToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(e: AppError<DATA_TYPE> | Error): ErrorObject<DATA_TYPE>;
19
+ export declare function _errorLikeToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(e: AppError<DATA_TYPE> | Error | ErrorLike): ErrorObject<DATA_TYPE>;
20
20
  export declare function _errorObjectToError<DATA_TYPE extends ErrorData, ERROR_TYPE extends Error>(o: ErrorObject<DATA_TYPE>, errorClass?: Class<ERROR_TYPE>): ERROR_TYPE;
21
21
  export declare function _isBackendErrorResponseObject(o: any): o is BackendErrorResponseObject;
22
22
  export declare function _isHttpRequestErrorObject(o: any): o is ErrorObject<HttpRequestErrorData>;
@@ -24,6 +24,7 @@ export declare function _isHttpRequestErrorObject(o: any): o is ErrorObject<Http
24
24
  * Note: any instance of AppError is also automatically an ErrorObject
25
25
  */
26
26
  export declare function _isErrorObject<DATA_TYPE extends ErrorData = ErrorData>(o: any): o is ErrorObject<DATA_TYPE>;
27
+ export declare function _isErrorLike(o: any): o is ErrorLike;
27
28
  /**
28
29
  * Convenience function to safely add properties to Error's `data` object
29
30
  * (even if it wasn't previously existing)
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._errorDataAppend = exports._isErrorObject = exports._isHttpRequestErrorObject = exports._isBackendErrorResponseObject = exports._errorObjectToError = exports._errorToErrorObject = exports._anyToErrorObject = exports._anyToError = void 0;
3
+ exports._errorDataAppend = exports._isErrorLike = exports._isErrorObject = exports._isHttpRequestErrorObject = exports._isBackendErrorResponseObject = exports._errorObjectToError = exports._errorLikeToErrorObject = exports._anyToErrorObject = exports._anyToError = void 0;
4
4
  const __1 = require("..");
5
5
  /**
6
6
  * Useful to ensure that error in `catch (err) { ... }`
@@ -38,8 +38,9 @@ exports._anyToError = _anyToError;
38
38
  */
39
39
  function _anyToErrorObject(o, errorData) {
40
40
  let eo;
41
- if (o instanceof Error) {
42
- eo = _errorToErrorObject(o);
41
+ // if (o instanceof Error) {
42
+ if (_isErrorLike(o)) {
43
+ eo = _errorLikeToErrorObject(o);
43
44
  }
44
45
  else {
45
46
  o = (0, __1._jsonParseIfPossible)(o);
@@ -49,6 +50,9 @@ function _anyToErrorObject(o, errorData) {
49
50
  else if (_isErrorObject(o)) {
50
51
  eo = o;
51
52
  }
53
+ else if (_isErrorLike(o)) {
54
+ eo = _errorLikeToErrorObject(o);
55
+ }
52
56
  else {
53
57
  // Here we are sure it has no `data` property,
54
58
  // so, fair to return `data: {}` in the end
@@ -66,19 +70,20 @@ function _anyToErrorObject(o, errorData) {
66
70
  return eo;
67
71
  }
68
72
  exports._anyToErrorObject = _anyToErrorObject;
69
- function _errorToErrorObject(e) {
73
+ function _errorLikeToErrorObject(e) {
70
74
  const obj = {
71
75
  name: e.name,
72
76
  message: e.message,
73
- data: { ...e.data },
74
- stack: e.stack,
77
+ data: { ...e.data }, // empty by default
75
78
  };
79
+ if (e.stack)
80
+ obj.stack = e.stack;
76
81
  if (e.cause) {
77
82
  obj.cause = _anyToErrorObject(e.cause);
78
83
  }
79
84
  return obj;
80
85
  }
81
- exports._errorToErrorObject = _errorToErrorObject;
86
+ exports._errorLikeToErrorObject = _errorLikeToErrorObject;
82
87
  function _errorObjectToError(o, errorClass = Error) {
83
88
  if (o instanceof errorClass)
84
89
  return o;
@@ -136,9 +141,10 @@ function _isErrorObject(o) {
136
141
  typeof o.data === 'object');
137
142
  }
138
143
  exports._isErrorObject = _isErrorObject;
139
- // export function _isErrorLike(o: any): o is ErrorLike {
140
- // return !!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string'
141
- // }
144
+ function _isErrorLike(o) {
145
+ return !!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string';
146
+ }
147
+ exports._isErrorLike = _isErrorLike;
142
148
  /**
143
149
  * Convenience function to safely add properties to Error's `data` object
144
150
  * (even if it wasn't previously existing)
@@ -156,7 +156,8 @@ class Fetcher {
156
156
  }
157
157
  catch (err) {
158
158
  // For example, CORS error would result in "TypeError: failed to fetch" here
159
- res.err = err;
159
+ // or, `fetch failed` with the cause of `unexpected redirect`
160
+ res.err = (0, error_util_1._anyToError)(err);
160
161
  res.ok = false;
161
162
  // important to set it to undefined, otherwise it can keep the previous value (from previous try)
162
163
  res.fetchResponse = undefined;
@@ -259,9 +260,10 @@ class Fetcher {
259
260
  clearTimeout(timeout);
260
261
  let cause;
261
262
  if (res.err) {
262
- // This is only possible on JSON.parse error (or CORS error!)
263
+ // This is only possible on JSON.parse error, or CORS error,
264
+ // or `unexpected redirect`
263
265
  // This check should go first, to avoid calling .text() twice (which will fail)
264
- cause = (0, error_util_1._errorToErrorObject)(res.err);
266
+ cause = (0, error_util_1._errorLikeToErrorObject)(res.err);
265
267
  }
266
268
  else if (res.fetchResponse) {
267
269
  const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
@@ -467,7 +469,7 @@ class Fetcher {
467
469
  ...this.cfg.init,
468
470
  method: opt.method || this.cfg.init.method,
469
471
  credentials: opt.credentials || this.cfg.init.credentials,
470
- redirect: opt.followRedirects ?? this.cfg.followRedirects ?? true ? 'follow' : 'error',
472
+ redirect: opt.redirect || 'follow',
471
473
  }, {
472
474
  headers: (0, object_util_1._mapKeys)(opt.headers || {}, k => k.toLowerCase()),
473
475
  }),
@@ -123,9 +123,11 @@ export interface FetcherOptions {
123
123
  body?: Blob | BufferSource | FormData | URLSearchParams | string;
124
124
  credentials?: RequestCredentials;
125
125
  /**
126
- * Default to true.
126
+ * Default to 'follow'.
127
+ * 'error' would throw on redirect.
128
+ * 'manual' will not throw, but return !ok response with 3xx status.
127
129
  */
128
- followRedirects?: boolean;
130
+ redirect?: RequestRedirect;
129
131
  headers?: Record<string, any>;
130
132
  mode?: FetcherMode;
131
133
  searchParams?: Record<string, any>;
@@ -59,10 +59,10 @@ function _stringifyAny(obj, opt = {}) {
59
59
  if ((0, error_util_1._isBackendErrorResponseObject)(obj)) {
60
60
  return _stringifyAny(obj.error, opt);
61
61
  }
62
- if (obj instanceof Error || (0, error_util_1._isErrorObject)(obj)) {
62
+ if (obj instanceof Error || (0, error_util_1._isErrorLike)(obj)) {
63
63
  const { includeErrorCause = true } = opt;
64
64
  //
65
- // Error or ErrorObject
65
+ // Error or ErrorLike
66
66
  //
67
67
  // Omit "default" error name as non-informative
68
68
  // UPD: no, it's still important to understand that we're dealing with Error and not just some string
@@ -31,8 +31,9 @@ export function _anyToError(o, errorClass = Error, errorData) {
31
31
  */
32
32
  export function _anyToErrorObject(o, errorData) {
33
33
  let eo;
34
- if (o instanceof Error) {
35
- eo = _errorToErrorObject(o);
34
+ // if (o instanceof Error) {
35
+ if (_isErrorLike(o)) {
36
+ eo = _errorLikeToErrorObject(o);
36
37
  }
37
38
  else {
38
39
  o = _jsonParseIfPossible(o);
@@ -42,6 +43,9 @@ export function _anyToErrorObject(o, errorData) {
42
43
  else if (_isErrorObject(o)) {
43
44
  eo = o;
44
45
  }
46
+ else if (_isErrorLike(o)) {
47
+ eo = _errorLikeToErrorObject(o);
48
+ }
45
49
  else {
46
50
  // Here we are sure it has no `data` property,
47
51
  // so, fair to return `data: {}` in the end
@@ -58,13 +62,14 @@ export function _anyToErrorObject(o, errorData) {
58
62
  Object.assign(eo.data, errorData);
59
63
  return eo;
60
64
  }
61
- export function _errorToErrorObject(e) {
65
+ export function _errorLikeToErrorObject(e) {
62
66
  const obj = {
63
67
  name: e.name,
64
68
  message: e.message,
65
- data: Object.assign({}, e.data),
66
- stack: e.stack,
69
+ data: Object.assign({}, e.data), // empty by default
67
70
  };
71
+ if (e.stack)
72
+ obj.stack = e.stack;
68
73
  if (e.cause) {
69
74
  obj.cause = _anyToErrorObject(e.cause);
70
75
  }
@@ -124,9 +129,9 @@ export function _isErrorObject(o) {
124
129
  typeof o.message === 'string' &&
125
130
  typeof o.data === 'object');
126
131
  }
127
- // export function _isErrorLike(o: any): o is ErrorLike {
128
- // return !!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string'
129
- // }
132
+ export function _isErrorLike(o) {
133
+ return !!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string';
134
+ }
130
135
  /**
131
136
  * Convenience function to safely add properties to Error's `data` object
132
137
  * (even if it wasn't previously existing)
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="dom"/>
2
2
  import { isServerSide } from '../env';
3
- import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
3
+ import { _anyToError, _anyToErrorObject, _errorLikeToErrorObject } from '../error/error.util';
4
4
  import { HttpRequestError } from '../error/httpRequestError';
5
5
  import { _clamp } from '../number/number.util';
6
6
  import { _filterNullishValues, _filterUndefinedValues, _mapKeys, _merge, _omit, _pick, } from '../object/object.util';
@@ -141,7 +141,8 @@ export class Fetcher {
141
141
  }
142
142
  catch (err) {
143
143
  // For example, CORS error would result in "TypeError: failed to fetch" here
144
- res.err = err;
144
+ // or, `fetch failed` with the cause of `unexpected redirect`
145
+ res.err = _anyToError(err);
145
146
  res.ok = false;
146
147
  // important to set it to undefined, otherwise it can keep the previous value (from previous try)
147
148
  res.fetchResponse = undefined;
@@ -245,9 +246,10 @@ export class Fetcher {
245
246
  clearTimeout(timeout);
246
247
  let cause;
247
248
  if (res.err) {
248
- // This is only possible on JSON.parse error (or CORS error!)
249
+ // This is only possible on JSON.parse error, or CORS error,
250
+ // or `unexpected redirect`
249
251
  // This check should go first, to avoid calling .text() twice (which will fail)
250
- cause = _errorToErrorObject(res.err);
252
+ cause = _errorLikeToErrorObject(res.err);
251
253
  }
252
254
  else if (res.fetchResponse) {
253
255
  const body = _jsonParseIfPossible(await res.fetchResponse.text());
@@ -431,7 +433,6 @@ export class Fetcher {
431
433
  return norm;
432
434
  }
433
435
  normalizeOptions(opt) {
434
- var _a, _b;
435
436
  const req = Object.assign(Object.assign(Object.assign(Object.assign({}, _pick(this.cfg, [
436
437
  'timeoutSeconds',
437
438
  'throwHttpErrors',
@@ -444,7 +445,7 @@ export class Fetcher {
444
445
  'logRequestBody',
445
446
  'logResponse',
446
447
  'logResponseBody',
447
- ])), { started: Date.now() }), _omit(opt, ['method', 'headers', 'credentials'])), { inputUrl: opt.url || '', fullUrl: opt.url || '', retry: Object.assign(Object.assign({}, this.cfg.retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign(Object.assign({}, this.cfg.init), { method: opt.method || this.cfg.init.method, credentials: opt.credentials || this.cfg.init.credentials, redirect: ((_b = (_a = opt.followRedirects) !== null && _a !== void 0 ? _a : this.cfg.followRedirects) !== null && _b !== void 0 ? _b : true) ? 'follow' : 'error' }), {
448
+ ])), { started: Date.now() }), _omit(opt, ['method', 'headers', 'credentials'])), { inputUrl: opt.url || '', fullUrl: opt.url || '', retry: Object.assign(Object.assign({}, this.cfg.retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign(Object.assign({}, this.cfg.init), { method: opt.method || this.cfg.init.method, credentials: opt.credentials || this.cfg.init.credentials, redirect: opt.redirect || 'follow' }), {
448
449
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
449
450
  }) });
450
451
  // setup url
@@ -1,4 +1,4 @@
1
- import { _isErrorObject, _isBackendErrorResponseObject } from '../error/error.util';
1
+ import { _isBackendErrorResponseObject, _isErrorLike } from '../error/error.util';
2
2
  import { _jsonParseIfPossible } from './json.util';
3
3
  import { _safeJsonStringify } from './safeJsonStringify';
4
4
  const supportsAggregateError = typeof globalThis.AggregateError === 'function';
@@ -55,10 +55,10 @@ export function _stringifyAny(obj, opt = {}) {
55
55
  if (_isBackendErrorResponseObject(obj)) {
56
56
  return _stringifyAny(obj.error, opt);
57
57
  }
58
- if (obj instanceof Error || _isErrorObject(obj)) {
58
+ if (obj instanceof Error || _isErrorLike(obj)) {
59
59
  const { includeErrorCause = true } = opt;
60
60
  //
61
- // Error or ErrorObject
61
+ // Error or ErrorLike
62
62
  //
63
63
  // Omit "default" error name as non-informative
64
64
  // UPD: no, it's still important to understand that we're dealing with Error and not just some string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.150.0",
3
+ "version": "14.151.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -104,13 +104,17 @@ export interface HttpRequestErrorData extends ErrorData {
104
104
  responseStatusCode: HttpStatusCode
105
105
  }
106
106
 
107
- // export interface ErrorLike {
108
- // name: string
109
- // message: string
110
- // stack?: string
111
- // data?: any
112
- // cause?: any
113
- // }
107
+ /**
108
+ * Sometimes there are cases when Errors come from unexpected places,
109
+ * where err is not instanceof Error, but still looks like Error.
110
+ */
111
+ export interface ErrorLike {
112
+ name: string
113
+ message: string
114
+ stack?: string
115
+ data?: any
116
+ cause?: any
117
+ }
114
118
 
115
119
  /**
116
120
  * Portable object that represents Error.
@@ -4,6 +4,7 @@ import type {
4
4
  BackendErrorResponseObject,
5
5
  Class,
6
6
  HttpRequestErrorData,
7
+ ErrorLike,
7
8
  } from '..'
8
9
  import { AppError, _jsonParseIfPossible, _stringifyAny } from '..'
9
10
 
@@ -53,8 +54,9 @@ export function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
53
54
  ): ErrorObject<DATA_TYPE> {
54
55
  let eo: ErrorObject<DATA_TYPE>
55
56
 
56
- if (o instanceof Error) {
57
- eo = _errorToErrorObject<DATA_TYPE>(o)
57
+ // if (o instanceof Error) {
58
+ if (_isErrorLike(o)) {
59
+ eo = _errorLikeToErrorObject(o)
58
60
  } else {
59
61
  o = _jsonParseIfPossible(o)
60
62
 
@@ -62,6 +64,8 @@ export function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
62
64
  eo = o.error as ErrorObject<DATA_TYPE>
63
65
  } else if (_isErrorObject(o)) {
64
66
  eo = o as ErrorObject<DATA_TYPE>
67
+ } else if (_isErrorLike(o)) {
68
+ eo = _errorLikeToErrorObject(o)
65
69
  } else {
66
70
  // Here we are sure it has no `data` property,
67
71
  // so, fair to return `data: {}` in the end
@@ -81,16 +85,17 @@ export function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
81
85
  return eo
82
86
  }
83
87
 
84
- export function _errorToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
85
- e: AppError<DATA_TYPE> | Error,
88
+ export function _errorLikeToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
89
+ e: AppError<DATA_TYPE> | Error | ErrorLike,
86
90
  ): ErrorObject<DATA_TYPE> {
87
91
  const obj: ErrorObject<DATA_TYPE> = {
88
92
  name: e.name,
89
93
  message: e.message,
90
94
  data: { ...(e as any).data }, // empty by default
91
- stack: e.stack,
92
95
  }
93
96
 
97
+ if (e.stack) obj.stack = e.stack
98
+
94
99
  if (e.cause) {
95
100
  obj.cause = _anyToErrorObject(e.cause)
96
101
  }
@@ -168,9 +173,9 @@ export function _isErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
168
173
  )
169
174
  }
170
175
 
171
- // export function _isErrorLike(o: any): o is ErrorLike {
172
- // return !!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string'
173
- // }
176
+ export function _isErrorLike(o: any): o is ErrorLike {
177
+ return !!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string'
178
+ }
174
179
 
175
180
  /**
176
181
  * Convenience function to safely add properties to Error's `data` object
@@ -150,9 +150,11 @@ export interface FetcherOptions {
150
150
 
151
151
  credentials?: RequestCredentials
152
152
  /**
153
- * Default to true.
153
+ * Default to 'follow'.
154
+ * 'error' would throw on redirect.
155
+ * 'manual' will not throw, but return !ok response with 3xx status.
154
156
  */
155
- followRedirects?: boolean
157
+ redirect?: RequestRedirect
156
158
 
157
159
  // Removing RequestInit from options to simplify FetcherOptions interface.
158
160
  // Will instead only add hand-picked useful options, such as `credentials`.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { isServerSide } from '../env'
4
4
  import { ErrorObject } from '../error/error.model'
5
- import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
5
+ import { _anyToError, _anyToErrorObject, _errorLikeToErrorObject } from '../error/error.util'
6
6
  import { HttpRequestError } from '../error/httpRequestError'
7
7
  import { _clamp } from '../number/number.util'
8
8
  import {
@@ -221,7 +221,8 @@ export class Fetcher {
221
221
  res.err = undefined
222
222
  } catch (err) {
223
223
  // For example, CORS error would result in "TypeError: failed to fetch" here
224
- res.err = err as Error
224
+ // or, `fetch failed` with the cause of `unexpected redirect`
225
+ res.err = _anyToError(err)
225
226
  res.ok = false
226
227
  // important to set it to undefined, otherwise it can keep the previous value (from previous try)
227
228
  res.fetchResponse = undefined
@@ -336,9 +337,10 @@ export class Fetcher {
336
337
  let cause: ErrorObject | undefined
337
338
 
338
339
  if (res.err) {
339
- // This is only possible on JSON.parse error (or CORS error!)
340
+ // This is only possible on JSON.parse error, or CORS error,
341
+ // or `unexpected redirect`
340
342
  // This check should go first, to avoid calling .text() twice (which will fail)
341
- cause = _errorToErrorObject(res.err)
343
+ cause = _errorLikeToErrorObject(res.err)
342
344
  } else if (res.fetchResponse) {
343
345
  const body = _jsonParseIfPossible(await res.fetchResponse.text())
344
346
  if (body) {
@@ -574,7 +576,7 @@ export class Fetcher {
574
576
  ...this.cfg.init,
575
577
  method: opt.method || this.cfg.init.method,
576
578
  credentials: opt.credentials || this.cfg.init.credentials,
577
- redirect: opt.followRedirects ?? this.cfg.followRedirects ?? true ? 'follow' : 'error',
579
+ redirect: opt.redirect || 'follow',
578
580
  },
579
581
  {
580
582
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
@@ -1,4 +1,4 @@
1
- import { _isErrorObject, _isBackendErrorResponseObject } from '../error/error.util'
1
+ import { _isBackendErrorResponseObject, _isErrorLike } from '../error/error.util'
2
2
  import type { Reviver } from '../types'
3
3
  import { _jsonParseIfPossible } from './json.util'
4
4
  import { _safeJsonStringify } from './safeJsonStringify'
@@ -92,10 +92,10 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
92
92
  return _stringifyAny(obj.error, opt)
93
93
  }
94
94
 
95
- if (obj instanceof Error || _isErrorObject(obj)) {
95
+ if (obj instanceof Error || _isErrorLike(obj)) {
96
96
  const { includeErrorCause = true } = opt
97
97
  //
98
- // Error or ErrorObject
98
+ // Error or ErrorLike
99
99
  //
100
100
 
101
101
  // Omit "default" error name as non-informative