@naturalcycles/js-lib 14.171.0 → 14.172.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.
@@ -17,6 +17,14 @@ export interface ErrorData {
17
17
  * Error id in some error tracking system (e.g Sentry).
18
18
  */
19
19
  errorId?: string;
20
+ /**
21
+ * If set - provides a short semi-user-friendly error message snippet,
22
+ * that would allow to give a hint to the user what went wrong,
23
+ * also to developers and CS to distinguish between different errors.
24
+ *
25
+ * It's not supposed to have full information about the error, just a small extract from it.
26
+ */
27
+ snippet?: string;
20
28
  /**
21
29
  * Set to true to force reporting this error (e.g to Sentry).
22
30
  * Useful to be able to force-report e.g a 4xx error, which by default wouldn't be reported.
@@ -35,11 +43,6 @@ export interface ErrorData {
35
43
  * E.g 0.1 will report 10% of errors (and ignore the 90%)
36
44
  */
37
45
  reportRate?: number;
38
- /**
39
- * Sometimes error.message gets "decorated" with extra information
40
- * (e.g frontend-lib adds a method, url, etc for all the errors)
41
- * `originalMessage` is used to preserve the original `error.message` as it came from the backend.
42
- */
43
46
  /**
44
47
  * Can be used by error-reporting tools (e.g Sentry).
45
48
  * If fingerprint is defined - it'll be used INSTEAD of default fingerprint of a tool.
@@ -18,6 +18,22 @@ export declare function _anyToError<ERROR_TYPE extends Error = Error>(o: any, er
18
18
  export declare function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(o: any, errorData?: Partial<DATA_TYPE>): ErrorObject<DATA_TYPE>;
19
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
+ export interface ErrorSnippetOptions {
22
+ /**
23
+ * Max length of the error line.
24
+ * Snippet may have multiple lines, one original and one per `cause`.
25
+ */
26
+ maxLineLength?: number;
27
+ maxLines?: number;
28
+ }
29
+ /**
30
+ * Provides a short semi-user-friendly error message snippet,
31
+ * that would allow to give a hint to the user what went wrong,
32
+ * also to developers and CS to distinguish between different errors.
33
+ *
34
+ * It's not supposed to have full information about the error, just a small extract from it.
35
+ */
36
+ export declare function _errorSnippet(err: any, opt?: ErrorSnippetOptions): string;
21
37
  export declare function _isBackendErrorResponseObject(o: any): o is BackendErrorResponseObject;
22
38
  export declare function _isHttpRequestErrorObject(o: any): o is ErrorObject<HttpRequestErrorData>;
23
39
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._errorDataAppend = exports._isErrorLike = exports._isErrorObject = exports._isHttpRequestErrorObject = exports._isBackendErrorResponseObject = exports._errorObjectToError = exports._errorLikeToErrorObject = exports._anyToErrorObject = exports._anyToError = void 0;
3
+ exports._errorDataAppend = exports._isErrorLike = exports._isErrorObject = exports._isHttpRequestErrorObject = exports._isBackendErrorResponseObject = exports._errorSnippet = 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,7 +38,6 @@ exports._anyToError = _anyToError;
38
38
  */
39
39
  function _anyToErrorObject(o, errorData) {
40
40
  let eo;
41
- // if (o instanceof Error) {
42
41
  if (_isErrorLike(o)) {
43
42
  eo = _errorLikeToErrorObject(o);
44
43
  }
@@ -71,6 +70,14 @@ function _anyToErrorObject(o, errorData) {
71
70
  }
72
71
  exports._anyToErrorObject = _anyToErrorObject;
73
72
  function _errorLikeToErrorObject(e) {
73
+ // If it's already an ErrorObject - just return it
74
+ // AppError satisfies ErrorObject interface
75
+ // Error does not satisfy (lacks `data`)
76
+ // UPD: no, we expect a "plain object" here as an output,
77
+ // because Error classes sometimes have non-enumerable properties (e.g data)
78
+ if (!(e instanceof Error) && _isErrorObject(e)) {
79
+ return e;
80
+ }
74
81
  const obj = {
75
82
  name: e.name,
76
83
  message: e.message,
@@ -129,6 +136,48 @@ function _errorObjectToError(o, errorClass = Error) {
129
136
  return err;
130
137
  }
131
138
  exports._errorObjectToError = _errorObjectToError;
139
+ // These "common" error classes will not be printed as part of the Error snippet
140
+ const commonErrorClasses = new Set([
141
+ 'Error',
142
+ 'AppError',
143
+ 'AssertionError',
144
+ 'HttpRequestError',
145
+ 'JoiValidationError',
146
+ ]);
147
+ /**
148
+ * Provides a short semi-user-friendly error message snippet,
149
+ * that would allow to give a hint to the user what went wrong,
150
+ * also to developers and CS to distinguish between different errors.
151
+ *
152
+ * It's not supposed to have full information about the error, just a small extract from it.
153
+ */
154
+ function _errorSnippet(err, opt = {}) {
155
+ const { maxLineLength = 60, maxLines = 3 } = opt;
156
+ const e = _anyToErrorObject(err);
157
+ const lines = [errorObjectToSnippet(e)];
158
+ let { cause } = e;
159
+ while (cause && lines.length < maxLines) {
160
+ lines.push('Caused by ' + errorObjectToSnippet(cause));
161
+ cause = cause.cause; // insert DiCaprio Inception meme
162
+ }
163
+ return lines.map(line => (0, __1._truncate)(line, maxLineLength)).join('\n');
164
+ function errorObjectToSnippet(e) {
165
+ // Return snippet if it was already prepared
166
+ if (e.data.snippet)
167
+ return e.data.snippet;
168
+ // Code already serves the purpose of the snippet, so we can just return it
169
+ if (e.data.code)
170
+ return e.data.code;
171
+ return [
172
+ !commonErrorClasses.has(e.name) && e.name,
173
+ // replace "1+ white space characters" with a single space
174
+ e.message.replaceAll(/\s+/gm, ' ').trim(),
175
+ ]
176
+ .filter(Boolean)
177
+ .join(': ');
178
+ }
179
+ }
180
+ exports._errorSnippet = _errorSnippet;
132
181
  function _isBackendErrorResponseObject(o) {
133
182
  return _isErrorObject(o?.error);
134
183
  }
@@ -1,4 +1,4 @@
1
- import { AppError, _jsonParseIfPossible, _stringifyAny } from '..';
1
+ import { AppError, _jsonParseIfPossible, _stringifyAny, _truncate } 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`).
@@ -31,7 +31,6 @@ 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
34
  if (_isErrorLike(o)) {
36
35
  eo = _errorLikeToErrorObject(o);
37
36
  }
@@ -63,6 +62,14 @@ export function _anyToErrorObject(o, errorData) {
63
62
  return eo;
64
63
  }
65
64
  export function _errorLikeToErrorObject(e) {
65
+ // If it's already an ErrorObject - just return it
66
+ // AppError satisfies ErrorObject interface
67
+ // Error does not satisfy (lacks `data`)
68
+ // UPD: no, we expect a "plain object" here as an output,
69
+ // because Error classes sometimes have non-enumerable properties (e.g data)
70
+ if (!(e instanceof Error) && _isErrorObject(e)) {
71
+ return e;
72
+ }
66
73
  const obj = {
67
74
  name: e.name,
68
75
  message: e.message,
@@ -119,6 +126,47 @@ export function _errorObjectToError(o, errorClass = Error) {
119
126
  }
120
127
  return err;
121
128
  }
129
+ // These "common" error classes will not be printed as part of the Error snippet
130
+ const commonErrorClasses = new Set([
131
+ 'Error',
132
+ 'AppError',
133
+ 'AssertionError',
134
+ 'HttpRequestError',
135
+ 'JoiValidationError',
136
+ ]);
137
+ /**
138
+ * Provides a short semi-user-friendly error message snippet,
139
+ * that would allow to give a hint to the user what went wrong,
140
+ * also to developers and CS to distinguish between different errors.
141
+ *
142
+ * It's not supposed to have full information about the error, just a small extract from it.
143
+ */
144
+ export function _errorSnippet(err, opt = {}) {
145
+ const { maxLineLength = 60, maxLines = 3 } = opt;
146
+ const e = _anyToErrorObject(err);
147
+ const lines = [errorObjectToSnippet(e)];
148
+ let { cause } = e;
149
+ while (cause && lines.length < maxLines) {
150
+ lines.push('Caused by ' + errorObjectToSnippet(cause));
151
+ cause = cause.cause; // insert DiCaprio Inception meme
152
+ }
153
+ return lines.map(line => _truncate(line, maxLineLength)).join('\n');
154
+ function errorObjectToSnippet(e) {
155
+ // Return snippet if it was already prepared
156
+ if (e.data.snippet)
157
+ return e.data.snippet;
158
+ // Code already serves the purpose of the snippet, so we can just return it
159
+ if (e.data.code)
160
+ return e.data.code;
161
+ return [
162
+ !commonErrorClasses.has(e.name) && e.name,
163
+ // replace "1+ white space characters" with a single space
164
+ e.message.replaceAll(/\s+/gm, ' ').trim(),
165
+ ]
166
+ .filter(Boolean)
167
+ .join(': ');
168
+ }
169
+ }
122
170
  export function _isBackendErrorResponseObject(o) {
123
171
  return _isErrorObject(o === null || o === void 0 ? void 0 : o.error);
124
172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.171.0",
3
+ "version": "14.172.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -14,7 +14,7 @@
14
14
  "devDependencies": {
15
15
  "@naturalcycles/bench-lib": "^1.5.0",
16
16
  "@naturalcycles/dev-lib": "^13.0.1",
17
- "@naturalcycles/nodejs-lib": "^12.33.4",
17
+ "@naturalcycles/nodejs-lib": "^13.0.1",
18
18
  "@naturalcycles/time-lib": "^3.5.1",
19
19
  "@types/crypto-js": "^4.1.1",
20
20
  "@types/node": "^20.1.0",
@@ -21,6 +21,15 @@ export interface ErrorData {
21
21
  */
22
22
  errorId?: string
23
23
 
24
+ /**
25
+ * If set - provides a short semi-user-friendly error message snippet,
26
+ * that would allow to give a hint to the user what went wrong,
27
+ * also to developers and CS to distinguish between different errors.
28
+ *
29
+ * It's not supposed to have full information about the error, just a small extract from it.
30
+ */
31
+ snippet?: string
32
+
24
33
  /**
25
34
  * Set to true to force reporting this error (e.g to Sentry).
26
35
  * Useful to be able to force-report e.g a 4xx error, which by default wouldn't be reported.
@@ -42,14 +51,6 @@ export interface ErrorData {
42
51
  */
43
52
  reportRate?: number
44
53
 
45
- /**
46
- * Sometimes error.message gets "decorated" with extra information
47
- * (e.g frontend-lib adds a method, url, etc for all the errors)
48
- * `originalMessage` is used to preserve the original `error.message` as it came from the backend.
49
- */
50
- // originalMessage?: string
51
- // use .cause.message instead
52
-
53
54
  /**
54
55
  * Can be used by error-reporting tools (e.g Sentry).
55
56
  * If fingerprint is defined - it'll be used INSTEAD of default fingerprint of a tool.
@@ -6,7 +6,7 @@ import type {
6
6
  HttpRequestErrorData,
7
7
  ErrorLike,
8
8
  } from '..'
9
- import { AppError, _jsonParseIfPossible, _stringifyAny } from '..'
9
+ import { AppError, _jsonParseIfPossible, _stringifyAny, _truncate } from '..'
10
10
 
11
11
  /**
12
12
  * Useful to ensure that error in `catch (err) { ... }`
@@ -54,7 +54,6 @@ export function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
54
54
  ): ErrorObject<DATA_TYPE> {
55
55
  let eo: ErrorObject<DATA_TYPE>
56
56
 
57
- // if (o instanceof Error) {
58
57
  if (_isErrorLike(o)) {
59
58
  eo = _errorLikeToErrorObject(o)
60
59
  } else {
@@ -88,6 +87,15 @@ export function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
88
87
  export function _errorLikeToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
89
88
  e: AppError<DATA_TYPE> | Error | ErrorLike,
90
89
  ): ErrorObject<DATA_TYPE> {
90
+ // If it's already an ErrorObject - just return it
91
+ // AppError satisfies ErrorObject interface
92
+ // Error does not satisfy (lacks `data`)
93
+ // UPD: no, we expect a "plain object" here as an output,
94
+ // because Error classes sometimes have non-enumerable properties (e.g data)
95
+ if (!(e instanceof Error) && _isErrorObject(e)) {
96
+ return e as ErrorObject<DATA_TYPE>
97
+ }
98
+
91
99
  const obj: ErrorObject<DATA_TYPE> = {
92
100
  name: e.name,
93
101
  message: e.message,
@@ -156,6 +164,64 @@ export function _errorObjectToError<DATA_TYPE extends ErrorData, ERROR_TYPE exte
156
164
  return err
157
165
  }
158
166
 
167
+ export interface ErrorSnippetOptions {
168
+ /**
169
+ * Max length of the error line.
170
+ * Snippet may have multiple lines, one original and one per `cause`.
171
+ */
172
+ maxLineLength?: number
173
+
174
+ maxLines?: number
175
+ }
176
+
177
+ // These "common" error classes will not be printed as part of the Error snippet
178
+ const commonErrorClasses = new Set([
179
+ 'Error',
180
+ 'AppError',
181
+ 'AssertionError',
182
+ 'HttpRequestError',
183
+ 'JoiValidationError',
184
+ ])
185
+
186
+ /**
187
+ * Provides a short semi-user-friendly error message snippet,
188
+ * that would allow to give a hint to the user what went wrong,
189
+ * also to developers and CS to distinguish between different errors.
190
+ *
191
+ * It's not supposed to have full information about the error, just a small extract from it.
192
+ */
193
+ export function _errorSnippet(err: any, opt: ErrorSnippetOptions = {}): string {
194
+ const { maxLineLength = 60, maxLines = 3 } = opt
195
+ const e = _anyToErrorObject(err)
196
+
197
+ const lines = [errorObjectToSnippet(e)]
198
+
199
+ let { cause } = e
200
+
201
+ while (cause && lines.length < maxLines) {
202
+ lines.push('Caused by ' + errorObjectToSnippet(cause))
203
+ cause = cause.cause // insert DiCaprio Inception meme
204
+ }
205
+
206
+ return lines.map(line => _truncate(line, maxLineLength)).join('\n')
207
+
208
+ function errorObjectToSnippet(e: ErrorObject): string {
209
+ // Return snippet if it was already prepared
210
+ if (e.data.snippet) return e.data.snippet
211
+
212
+ // Code already serves the purpose of the snippet, so we can just return it
213
+ if (e.data.code) return e.data.code
214
+
215
+ return [
216
+ !commonErrorClasses.has(e.name) && e.name,
217
+ // replace "1+ white space characters" with a single space
218
+ e.message.replaceAll(/\s+/gm, ' ').trim(),
219
+ ]
220
+ .filter(Boolean)
221
+ .join(': ')
222
+ }
223
+ }
224
+
159
225
  export function _isBackendErrorResponseObject(o: any): o is BackendErrorResponseObject {
160
226
  return _isErrorObject(o?.error)
161
227
  }