@naturalcycles/js-lib 14.159.0 → 14.161.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 (53) hide show
  1. package/dist/{lazy.d.ts → define.d.ts} +26 -0
  2. package/dist/define.js +118 -0
  3. package/dist/error/app.error.d.ts +16 -3
  4. package/dist/error/app.error.js +22 -19
  5. package/dist/error/assert.d.ts +1 -1
  6. package/dist/error/assert.js +2 -2
  7. package/dist/error/error.model.d.ts +7 -0
  8. package/dist/error/error.util.js +29 -22
  9. package/dist/error/httpRequestError.d.ts +2 -2
  10. package/dist/error/httpRequestError.js +7 -2
  11. package/dist/error/jsonParseError.d.ts +2 -2
  12. package/dist/error/jsonParseError.js +5 -2
  13. package/dist/error/try.js +3 -1
  14. package/dist/http/fetcher.js +6 -3
  15. package/dist/http/fetcher.model.d.ts +1 -0
  16. package/dist/http/fetcher.model.js +1 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/object/object.util.d.ts +16 -11
  20. package/dist/object/object.util.js +29 -12
  21. package/dist/promise/pTimeout.d.ts +3 -3
  22. package/dist/promise/pTimeout.js +2 -2
  23. package/dist/types.d.ts +9 -2
  24. package/dist/types.js +9 -2
  25. package/dist-esm/{lazy.js → define.js} +38 -0
  26. package/dist-esm/error/app.error.js +22 -19
  27. package/dist-esm/error/assert.js +2 -2
  28. package/dist-esm/error/error.util.js +30 -23
  29. package/dist-esm/error/httpRequestError.js +7 -2
  30. package/dist-esm/error/jsonParseError.js +5 -2
  31. package/dist-esm/error/try.js +3 -1
  32. package/dist-esm/http/fetcher.js +6 -3
  33. package/dist-esm/http/fetcher.model.js +1 -0
  34. package/dist-esm/index.js +1 -1
  35. package/dist-esm/object/object.util.js +27 -11
  36. package/dist-esm/promise/pTimeout.js +2 -2
  37. package/dist-esm/types.js +8 -1
  38. package/package.json +1 -1
  39. package/src/{lazy.ts → define.ts} +68 -0
  40. package/src/error/app.error.ts +39 -22
  41. package/src/error/assert.ts +2 -2
  42. package/src/error/error.model.ts +8 -0
  43. package/src/error/error.util.ts +31 -25
  44. package/src/error/httpRequestError.ts +9 -3
  45. package/src/error/jsonParseError.ts +7 -8
  46. package/src/error/try.ts +7 -1
  47. package/src/http/fetcher.model.ts +2 -0
  48. package/src/http/fetcher.ts +6 -3
  49. package/src/index.ts +1 -1
  50. package/src/object/object.util.ts +51 -30
  51. package/src/promise/pTimeout.ts +4 -4
  52. package/src/types.ts +12 -2
  53. package/dist/lazy.js +0 -65
package/dist/types.d.ts CHANGED
@@ -120,7 +120,7 @@ export type ValuesOf<T extends readonly any[]> = T[number];
120
120
  */
121
121
  export type ValueOf<T> = T[keyof T];
122
122
  export type KeyValueTuple<K, V> = [key: K, value: V];
123
- export type ObjectMapper<OBJ, OUT> = (key: string, value: Exclude<OBJ[keyof OBJ], undefined>, obj: OBJ) => OUT;
123
+ export type ObjectMapper<OBJ, OUT> = (key: keyof OBJ, value: Exclude<OBJ[keyof OBJ], undefined>, obj: OBJ) => OUT;
124
124
  export type ObjectPredicate<OBJ> = (key: keyof OBJ, value: Exclude<OBJ[keyof OBJ], undefined>, obj: OBJ) => boolean;
125
125
  /**
126
126
  * Allows to identify instance of Class by `instanceId`.
@@ -185,10 +185,17 @@ export declare const _stringMapValues: <T>(m: StringMap<T>) => T[];
185
185
  */
186
186
  export declare const _stringMapEntries: <T>(m: StringMap<T>) => [k: string, v: T][];
187
187
  /**
188
- * Like `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
188
+ * Alias of `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
189
189
  * This is how TypeScript should work, actually.
190
190
  */
191
191
  export declare const _objectKeys: <T extends AnyObject>(obj: T) => (keyof T)[];
192
+ /**
193
+ * Alias of `Object.entries`, but returns better-typed output.
194
+ *
195
+ * So e.g you can use _objectEntries(obj).map([k, v] => {})
196
+ * and `k` will be `keyof obj` instead of generic `string`.
197
+ */
198
+ export declare const _objectEntries: <T extends AnyObject>(obj: T) => [k: keyof T, v: T[keyof T]][];
192
199
  export type NullishValue = null | undefined;
193
200
  export type FalsyValue = false | '' | 0 | null | undefined;
194
201
  /**
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._objectAssign = exports._typeCast = exports._objectKeys = exports._stringMapEntries = exports._stringMapValues = exports._passNothingPredicate = exports._passthroughPredicate = exports._noop = exports._passUndefinedMapper = exports._passthroughMapper = exports.SKIP = exports.END = void 0;
3
+ exports._objectAssign = exports._typeCast = exports._objectEntries = exports._objectKeys = exports._stringMapEntries = exports._stringMapValues = exports._passNothingPredicate = exports._passthroughPredicate = exports._noop = exports._passUndefinedMapper = exports._passthroughMapper = exports.SKIP = exports.END = void 0;
4
4
  /**
5
5
  * Symbol to indicate END of Sequence.
6
6
  */
@@ -33,10 +33,17 @@ exports._stringMapValues = Object.values;
33
33
  */
34
34
  exports._stringMapEntries = Object.entries;
35
35
  /**
36
- * Like `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
36
+ * Alias of `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
37
37
  * This is how TypeScript should work, actually.
38
38
  */
39
39
  exports._objectKeys = Object.keys;
40
+ /**
41
+ * Alias of `Object.entries`, but returns better-typed output.
42
+ *
43
+ * So e.g you can use _objectEntries(obj).map([k, v] => {})
44
+ * and `k` will be `keyof obj` instead of generic `string`.
45
+ */
46
+ exports._objectEntries = Object.entries;
40
47
  /**
41
48
  * Utility function that helps to cast *existing variable* to needed type T.
42
49
  *
@@ -1,3 +1,5 @@
1
+ import { _mapObject, _mapValues } from './object/object.util';
2
+ import { SKIP } from './types';
1
3
  /**
2
4
  * const value = lazyValue(() => expensiveComputation())
3
5
  *
@@ -57,3 +59,39 @@ export function _defineLazyProps(obj, props) {
57
59
  Object.entries(props).forEach(([k, fn]) => _defineLazyProperty(obj, k, fn));
58
60
  return obj;
59
61
  }
62
+ /**
63
+ * Same as Object.defineProperty, but with better (least restricting) defaults.
64
+ *
65
+ * Defaults are:
66
+ * writable: true
67
+ * configurable: true
68
+ * enumerable: true
69
+ * value: existing obj[prop] value
70
+ *
71
+ * Original defaults:
72
+ * writable: false
73
+ * configurable: false
74
+ * enumerable: false
75
+ * value: existing obj[prop] value
76
+ *
77
+ */
78
+ export function _defineProperty(obj, prop, pd) {
79
+ return Object.defineProperty(obj, prop, Object.assign({ writable: true, configurable: true, enumerable: true }, pd));
80
+ }
81
+ /**
82
+ * Object.defineProperties with better defaults.
83
+ * See _defineProperty for exact defaults definition.
84
+ */
85
+ export function _defineProps(obj, props) {
86
+ return Object.defineProperties(obj, _mapValues(props, (k, pd) => (Object.assign({ writable: true, configurable: true, enumerable: true }, pd))));
87
+ }
88
+ /**
89
+ * Like _defineProps, but skips props with nullish values.
90
+ */
91
+ export function _defineNonNullishProps(obj, props) {
92
+ return _defineProps(obj, _mapObject(props, (k, pd) => {
93
+ if (pd.value === null || pd.value === undefined)
94
+ return SKIP;
95
+ return [k, pd];
96
+ }));
97
+ }
@@ -3,29 +3,26 @@
3
3
  *
4
4
  * message - "technical" message. Frontend decides to show it or not.
5
5
  * data - optional "any" payload.
6
- * data.userMessage - if present, will be displayed to the User as is.
6
+ * data.userFriendly - if present, will be displayed to the User as is.
7
7
  *
8
8
  * Based on: https://medium.com/@xpl/javascript-deriving-from-error-properly-8d2f8f315801
9
9
  */
10
10
  export class AppError extends Error {
11
- constructor(message, data = {}, cause, name) {
11
+ constructor(message, data = {}, opt = {}) {
12
12
  super(message);
13
- Object.defineProperty(this, 'name', {
14
- value: name || this.constructor.name,
15
- configurable: true,
16
- writable: true,
17
- });
18
- // this is to allow changing this.constuctor.name to a non-minified version
19
- Object.defineProperty(this.constructor, 'name', {
20
- value: name || this.constructor.name,
21
- configurable: true,
22
- writable: true,
23
- });
24
- Object.defineProperty(this, 'data', {
25
- value: data,
26
- writable: true,
27
- configurable: true,
28
- enumerable: false,
13
+ const { name = this.constructor.name, cause } = opt;
14
+ Object.defineProperties(this, {
15
+ name: {
16
+ value: name,
17
+ configurable: true,
18
+ writable: true,
19
+ },
20
+ data: {
21
+ value: data,
22
+ writable: true,
23
+ configurable: true,
24
+ enumerable: false,
25
+ },
29
26
  });
30
27
  if (cause) {
31
28
  Object.defineProperty(this, 'cause', {
@@ -33,9 +30,15 @@ export class AppError extends Error {
33
30
  value: cause,
34
31
  writable: true,
35
32
  configurable: true,
36
- enumerable: false,
33
+ enumerable: true, // unlike standard - setting it to true for "visibility"
37
34
  });
38
35
  }
36
+ // this is to allow changing this.constuctor.name to a non-minified version
37
+ Object.defineProperty(this.constructor, 'name', {
38
+ value: name,
39
+ configurable: true,
40
+ writable: true,
41
+ });
39
42
  // todo: check if it's needed at all!
40
43
  // if (Error.captureStackTrace) {
41
44
  // Error.captureStackTrace(this, this.constructor)
@@ -106,7 +106,7 @@ export function _assertTypeOf(v, expectedType, message) {
106
106
  }
107
107
  }
108
108
  export class AssertionError extends AppError {
109
- constructor(message, data = {}, cause) {
110
- super(message, data, cause, 'AssertionError');
109
+ constructor(message, data) {
110
+ super(message, data, { name: 'AssertionError' });
111
111
  }
112
112
  }
@@ -1,4 +1,4 @@
1
- import { _jsonParseIfPossible, _stringifyAny } from '..';
1
+ import { AppError, _jsonParseIfPossible, _stringifyAny } from '..';
2
2
  /**
3
3
  * Useful to ensure that error in `catch (err) { ... }`
4
4
  * is indeed an Error (and not e.g `string` or `undefined`).
@@ -78,36 +78,43 @@ export function _errorLikeToErrorObject(e) {
78
78
  export function _errorObjectToError(o, errorClass = Error) {
79
79
  if (o instanceof errorClass)
80
80
  return o;
81
- const err = new errorClass(o.message);
81
+ // Here we pass constructor values assuming it's AppError or sub-class of it
82
+ // If not - will be checked at the next step
83
+ // We cannot check `if (errorClass instanceof AppError)`, only `err instanceof AppError`
84
+ const { name, cause } = o;
85
+ const err = new errorClass(o.message, o.data, { name, cause });
82
86
  // name: err.name, // cannot be assigned to a readonly property like this
83
87
  // stack: o.stack, // also readonly e.g in Firefox
84
- Object.defineProperty(err, 'name', {
85
- value: o.name,
86
- configurable: true,
87
- writable: true,
88
- });
89
- Object.defineProperty(err.constructor, 'name', {
90
- value: o.name,
91
- configurable: true,
92
- writable: true,
93
- });
94
- Object.defineProperty(err, 'data', {
95
- value: o.data,
96
- writable: true,
97
- configurable: true,
98
- enumerable: false,
99
- });
100
88
  if (o.stack) {
101
89
  Object.defineProperty(err, 'stack', {
102
90
  value: o.stack,
103
91
  });
104
92
  }
105
- if (o.cause) {
106
- Object.defineProperty(err, 'cause', {
107
- value: o.cause,
108
- writable: true,
93
+ if (!(err instanceof AppError)) {
94
+ // Following actions are only needed for non-AppError-like errors
95
+ Object.defineProperties(err, {
96
+ name: {
97
+ value: name,
98
+ configurable: true,
99
+ writable: true,
100
+ },
101
+ data: {
102
+ value: o.data,
103
+ writable: true,
104
+ configurable: true,
105
+ enumerable: false,
106
+ },
107
+ cause: {
108
+ value: cause,
109
+ writable: true,
110
+ configurable: true,
111
+ enumerable: true,
112
+ },
113
+ });
114
+ Object.defineProperty(err.constructor, 'name', {
115
+ value: name,
109
116
  configurable: true,
110
- enumerable: false,
117
+ writable: true,
111
118
  });
112
119
  }
113
120
  return err;
@@ -17,7 +17,12 @@ import { AppError } from './app.error';
17
17
  * (by default).
18
18
  */
19
19
  export class HttpRequestError extends AppError {
20
- constructor(message, data, cause) {
21
- super(message, data, cause, 'HttpRequestError');
20
+ constructor(message, data, opt) {
21
+ if (data.response) {
22
+ Object.defineProperty(data, 'response', {
23
+ enumerable: false,
24
+ });
25
+ }
26
+ super(message, data, Object.assign(Object.assign({}, opt), { name: 'HttpRequestError' }));
22
27
  }
23
28
  }
@@ -1,7 +1,10 @@
1
1
  import { _truncateMiddle } from '../string/string.util';
2
2
  import { AppError } from './app.error';
3
3
  export class JsonParseError extends AppError {
4
- constructor(data, cause) {
5
- super(['Failed to parse', data.text && _truncateMiddle(data.text, 200)].filter(Boolean).join(': '), data, cause, 'JsonParseError');
4
+ constructor(data) {
5
+ const message = ['Failed to parse', data.text && _truncateMiddle(data.text, 200)]
6
+ .filter(Boolean)
7
+ .join(': ');
8
+ super(message, data, { name: 'JsonParseError' });
6
9
  }
7
10
  }
@@ -52,7 +52,9 @@ export async function pTry(promise, errorClass) {
52
52
  */
53
53
  export class UnexpectedPassError extends AppError {
54
54
  constructor() {
55
- super('expected error was not thrown', {}, undefined, 'UnexpectedPassError');
55
+ super('expected error was not thrown', {}, {
56
+ name: 'UnexpectedPassError',
57
+ });
56
58
  }
57
59
  }
58
60
  /**
@@ -295,6 +295,7 @@ export class Fetcher {
295
295
  });
296
296
  const message = [(_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status, res.signature].filter(Boolean).join(' ');
297
297
  res.err = new HttpRequestError(message, _filterNullishValues({
298
+ response: res.fetchResponse,
298
299
  responseStatusCode: ((_b = res.fetchResponse) === null || _b === void 0 ? void 0 : _b.status) || 0,
299
300
  // These properties are provided to be used in e.g custom Sentry error grouping
300
301
  // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
@@ -302,11 +303,13 @@ export class Fetcher {
302
303
  // method: req.method,
303
304
  // tryCount: req.tryCount,
304
305
  requestUrl: res.req.fullUrl,
305
- requestBaseUrl: this.cfg.baseUrl || null,
306
+ requestBaseUrl: this.cfg.baseUrl || undefined,
306
307
  requestMethod: res.req.init.method,
307
308
  requestSignature: res.signature,
308
309
  requestDuration: Date.now() - res.req.started,
309
- }), cause);
310
+ }), {
311
+ cause,
312
+ });
310
313
  await this.processRetry(res);
311
314
  }
312
315
  async processRetry(res) {
@@ -512,7 +515,7 @@ export class Fetcher {
512
515
  const searchParams = _filterUndefinedValues(Object.assign(Object.assign({}, this.cfg.searchParams), opt.searchParams));
513
516
  if (Object.keys(searchParams).length) {
514
517
  const qs = new URLSearchParams(searchParams).toString();
515
- req.fullUrl += req.fullUrl.includes('?') ? '&' : '?' + qs;
518
+ req.fullUrl += (req.fullUrl.includes('?') ? '&' : '?') + qs;
516
519
  }
517
520
  // setup request body
518
521
  if (opt.json !== undefined) {
@@ -1 +1,2 @@
1
+ /// <reference lib="dom"/>
1
2
  export {};
package/dist-esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './array/array.util';
2
- export * from './lazy';
2
+ export * from './define';
3
3
  export * from './string/url.util';
4
4
  export * from './array/range';
5
5
  export * from './decorators/createPromiseDecorator';
@@ -1,4 +1,5 @@
1
1
  import { _isEmpty, _isObject } from '../is.util';
2
+ import { _objectEntries, SKIP } from '../types';
2
3
  /**
3
4
  * Returns clone of `obj` with only `props` preserved.
4
5
  * Opposite of Omit.
@@ -82,27 +83,27 @@ export function _filterObject(obj, predicate, mutate = false) {
82
83
  * 'pebbles': { 'user': 'pebbles', 'age': 1 }
83
84
  * }
84
85
  *
85
- * _.mapValues(users, function(_key, value) { return value.age; });
86
+ * _mapValues(users, (_key, value) => value.age)
86
87
  * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
87
88
  *
88
- * // The `_.property` iteratee shorthand.
89
- * _.mapValues(users, 'age')
90
- * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
89
+ * To skip some key-value pairs - use _mapObject instead.
91
90
  */
92
91
  export function _mapValues(obj, mapper, mutate = false) {
93
- return Object.entries(obj).reduce((map, [k, v]) => {
92
+ return _objectEntries(obj).reduce((map, [k, v]) => {
94
93
  map[k] = mapper(k, v, obj);
95
94
  return map;
96
- }, (mutate ? obj : {}));
95
+ }, mutate ? obj : {});
97
96
  }
98
97
  /**
99
98
  * _.mapKeys({ 'a': 1, 'b': 2 }, (key, value) => key + value)
100
99
  * // => { 'a1': 1, 'b2': 2 }
101
100
  *
102
101
  * Does not support `mutate` flag.
102
+ *
103
+ * To skip some key-value pairs - use _mapObject instead.
103
104
  */
104
105
  export function _mapKeys(obj, mapper) {
105
- return Object.entries(obj).reduce((map, [k, v]) => {
106
+ return _objectEntries(obj).reduce((map, [k, v]) => {
106
107
  map[mapper(k, v, obj)] = v;
107
108
  return map;
108
109
  }, {});
@@ -119,15 +120,14 @@ export function _mapKeys(obj, mapper) {
119
120
  * 0 - key of returned object (string)
120
121
  * 1 - value of returned object (any)
121
122
  *
122
- * If predicate returns falsy value (e.g undefined), or a tuple where key (first item) is falsy - then such key/value pair is ignored (filtered out).
123
+ * If predicate returns SKIP symbol - such key/value pair is ignored (filtered out).
123
124
  *
124
125
  * Non-string keys are passed via String(...)
125
126
  */
126
127
  export function _mapObject(obj, mapper) {
127
128
  return Object.entries(obj).reduce((map, [k, v]) => {
128
- const r = mapper(k, v, obj) || [];
129
- if (r[0]) {
130
- ;
129
+ const r = mapper(k, v, obj);
130
+ if (r !== SKIP) {
131
131
  map[r[0]] = r[1];
132
132
  }
133
133
  return map;
@@ -342,3 +342,19 @@ export function _has(obj, path) {
342
342
  const v = _get(obj, path);
343
343
  return v !== undefined && v !== null;
344
344
  }
345
+ /**
346
+ * Does Object.freeze recursively for given object.
347
+ *
348
+ * Based on: https://github.com/substack/deep-freeze/blob/master/index.js
349
+ */
350
+ export function _deepFreeze(o) {
351
+ Object.freeze(o);
352
+ Object.getOwnPropertyNames(o).forEach(prop => {
353
+ if (o.hasOwnProperty(prop) && // eslint-disable-line no-prototype-builtins
354
+ o[prop] !== null &&
355
+ (typeof o[prop] === 'object' || typeof o[prop] === 'function') &&
356
+ !Object.isFrozen(o[prop])) {
357
+ _deepFreeze(o[prop]);
358
+ }
359
+ });
360
+ }
@@ -1,8 +1,8 @@
1
1
  import { AppError } from '../error/app.error';
2
2
  import { _errorDataAppend } from '../error/error.util';
3
3
  export class TimeoutError extends AppError {
4
- constructor(message, data = {}, cause) {
5
- super(message, data, cause, 'TimeoutError');
4
+ constructor(message, data, opt) {
5
+ super(message, data, Object.assign(Object.assign({}, opt), { name: 'TimeoutError' }));
6
6
  }
7
7
  }
8
8
  /**
package/dist-esm/types.js CHANGED
@@ -25,10 +25,17 @@ export const _stringMapValues = Object.values;
25
25
  */
26
26
  export const _stringMapEntries = Object.entries;
27
27
  /**
28
- * Like `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
28
+ * Alias of `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
29
29
  * This is how TypeScript should work, actually.
30
30
  */
31
31
  export const _objectKeys = Object.keys;
32
+ /**
33
+ * Alias of `Object.entries`, but returns better-typed output.
34
+ *
35
+ * So e.g you can use _objectEntries(obj).map([k, v] => {})
36
+ * and `k` will be `keyof obj` instead of generic `string`.
37
+ */
38
+ export const _objectEntries = Object.entries;
32
39
  /**
33
40
  * Utility function that helps to cast *existing variable* to needed type T.
34
41
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.159.0",
3
+ "version": "14.161.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -1,3 +1,5 @@
1
+ import { _mapObject, _mapValues } from './object/object.util'
2
+ import { SKIP } from './types'
1
3
  import type { AnyFunction, AnyObject } from './types'
2
4
 
3
5
  /**
@@ -72,3 +74,69 @@ export function _defineLazyProps<OBJ extends AnyObject>(
72
74
  Object.entries(props).forEach(([k, fn]) => _defineLazyProperty(obj, k, fn!))
73
75
  return obj
74
76
  }
77
+
78
+ /**
79
+ * Same as Object.defineProperty, but with better (least restricting) defaults.
80
+ *
81
+ * Defaults are:
82
+ * writable: true
83
+ * configurable: true
84
+ * enumerable: true
85
+ * value: existing obj[prop] value
86
+ *
87
+ * Original defaults:
88
+ * writable: false
89
+ * configurable: false
90
+ * enumerable: false
91
+ * value: existing obj[prop] value
92
+ *
93
+ */
94
+ export function _defineProperty<T extends AnyObject>(
95
+ obj: T,
96
+ prop: keyof T,
97
+ pd: PropertyDescriptor,
98
+ ): T {
99
+ return Object.defineProperty(obj, prop, {
100
+ writable: true,
101
+ configurable: true,
102
+ enumerable: true,
103
+ // value: obj[prop], // existing value is already kept by default
104
+ ...pd,
105
+ })
106
+ }
107
+
108
+ /**
109
+ * Object.defineProperties with better defaults.
110
+ * See _defineProperty for exact defaults definition.
111
+ */
112
+ export function _defineProps<T extends AnyObject>(
113
+ obj: T,
114
+ props: Partial<Record<keyof T, PropertyDescriptor>>,
115
+ ): T {
116
+ return Object.defineProperties(
117
+ obj,
118
+ _mapValues(props, (k, pd) => ({
119
+ writable: true,
120
+ configurable: true,
121
+ enumerable: true,
122
+ // value: obj[k], // existing value is already kept by default
123
+ ...pd,
124
+ })) as PropertyDescriptorMap,
125
+ )
126
+ }
127
+
128
+ /**
129
+ * Like _defineProps, but skips props with nullish values.
130
+ */
131
+ export function _defineNonNullishProps<T extends AnyObject>(
132
+ obj: T,
133
+ props: Partial<Record<keyof T, PropertyDescriptor>>,
134
+ ): T {
135
+ return _defineProps(
136
+ obj,
137
+ _mapObject(props, (k, pd) => {
138
+ if (pd.value === null || pd.value === undefined) return SKIP
139
+ return [k as string, pd]
140
+ }),
141
+ )
142
+ }
@@ -5,7 +5,7 @@ import type { ErrorData, ErrorObject } from './error.model'
5
5
  *
6
6
  * message - "technical" message. Frontend decides to show it or not.
7
7
  * data - optional "any" payload.
8
- * data.userMessage - if present, will be displayed to the User as is.
8
+ * data.userFriendly - if present, will be displayed to the User as is.
9
9
  *
10
10
  * Based on: https://medium.com/@xpl/javascript-deriving-from-error-properly-8d2f8f315801
11
11
  */
@@ -13,31 +13,26 @@ export class AppError<DATA_TYPE extends ErrorData = ErrorData> extends Error {
13
13
  data!: DATA_TYPE
14
14
 
15
15
  /**
16
- * cause here is normalized to be an ErrorObject
16
+ * `cause` here is normalized to be an ErrorObject
17
17
  */
18
18
  override cause?: ErrorObject
19
19
 
20
- constructor(message: string, data = {} as DATA_TYPE, cause?: ErrorObject, name?: string) {
20
+ constructor(message: string, data = {} as DATA_TYPE, opt: AppErrorOptions = {}) {
21
21
  super(message)
22
+ const { name = this.constructor.name, cause } = opt
22
23
 
23
- Object.defineProperty(this, 'name', {
24
- value: name || this.constructor.name,
25
- configurable: true,
26
- writable: true,
27
- })
28
-
29
- // this is to allow changing this.constuctor.name to a non-minified version
30
- Object.defineProperty(this.constructor, 'name', {
31
- value: name || this.constructor.name,
32
- configurable: true,
33
- writable: true,
34
- })
35
-
36
- Object.defineProperty(this, 'data', {
37
- value: data,
38
- writable: true,
39
- configurable: true,
40
- enumerable: false,
24
+ Object.defineProperties(this, {
25
+ name: {
26
+ value: name,
27
+ configurable: true,
28
+ writable: true,
29
+ },
30
+ data: {
31
+ value: data,
32
+ writable: true,
33
+ configurable: true,
34
+ enumerable: false,
35
+ },
41
36
  })
42
37
 
43
38
  if (cause) {
@@ -46,10 +41,17 @@ export class AppError<DATA_TYPE extends ErrorData = ErrorData> extends Error {
46
41
  value: cause,
47
42
  writable: true,
48
43
  configurable: true,
49
- enumerable: false,
44
+ enumerable: true, // unlike standard - setting it to true for "visibility"
50
45
  })
51
46
  }
52
47
 
48
+ // this is to allow changing this.constuctor.name to a non-minified version
49
+ Object.defineProperty(this.constructor, 'name', {
50
+ value: name,
51
+ configurable: true,
52
+ writable: true,
53
+ })
54
+
53
55
  // todo: check if it's needed at all!
54
56
  // if (Error.captureStackTrace) {
55
57
  // Error.captureStackTrace(this, this.constructor)
@@ -62,3 +64,18 @@ export class AppError<DATA_TYPE extends ErrorData = ErrorData> extends Error {
62
64
  // }
63
65
  }
64
66
  }
67
+
68
+ /**
69
+ * Extra options for AppError constructor.
70
+ */
71
+ export interface AppErrorOptions {
72
+ /**
73
+ * Overrides Error.name and Error.constructor.name
74
+ */
75
+ name?: string
76
+
77
+ /**
78
+ * Sets Error.cause
79
+ */
80
+ cause?: ErrorObject
81
+ }
@@ -152,7 +152,7 @@ export function _assertTypeOf<T>(v: any, expectedType: string, message?: string)
152
152
  }
153
153
 
154
154
  export class AssertionError extends AppError {
155
- constructor(message: string, data = {}, cause?: ErrorObject) {
156
- super(message, data, cause, 'AssertionError')
155
+ constructor(message: string, data?: ErrorData) {
156
+ super(message, data, { name: 'AssertionError' })
157
157
  }
158
158
  }
@@ -85,6 +85,14 @@ export interface ErrorData {
85
85
  }
86
86
 
87
87
  export interface HttpRequestErrorData extends ErrorData {
88
+ /**
89
+ * Actual `fetch` Response as it was returned.
90
+ * Note that not every Request has a Response, hence it's optional.
91
+ *
92
+ * Non-enumerable.
93
+ */
94
+ response?: Response
95
+
88
96
  requestUrl: string
89
97
  requestBaseUrl?: string
90
98
  requestMethod: HttpMethod