@naturalcycles/js-lib 14.171.0 → 14.173.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.
- package/dist/error/assert.d.ts +0 -4
- package/dist/error/assert.js +7 -14
- package/dist/error/error.model.d.ts +8 -5
- package/dist/error/error.util.d.ts +96 -1
- package/dist/error/error.util.js +167 -3
- package/dist/error/try.d.ts +0 -9
- package/dist/error/try.js +6 -19
- package/dist/http/fetcher.d.ts +1 -1
- package/dist/http/fetcher.js +3 -4
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/promise/pTimeout.d.ts +1 -4
- package/dist/promise/pTimeout.js +2 -9
- package/dist/string/json.util.js +2 -2
- package/dist-esm/error/assert.js +1 -7
- package/dist-esm/error/error.util.js +159 -2
- package/dist-esm/error/try.js +1 -13
- package/dist-esm/http/fetcher.js +2 -3
- package/dist-esm/index.js +0 -3
- package/dist-esm/promise/pTimeout.js +1 -7
- package/dist-esm/string/json.util.js +1 -1
- package/package.json +2 -2
- package/src/error/assert.ts +1 -8
- package/src/error/error.model.ts +9 -8
- package/src/error/error.util.ts +230 -2
- package/src/error/try.ts +1 -18
- package/src/http/fetcher.ts +8 -3
- package/src/index.ts +0 -3
- package/src/promise/pTimeout.ts +1 -8
- package/src/string/json.util.ts +1 -1
- package/dist/error/app.error.d.ts +0 -31
- package/dist/error/app.error.js +0 -57
- package/dist/error/httpRequestError.d.ts +0 -28
- package/dist/error/httpRequestError.js +0 -32
- package/dist/error/jsonParseError.d.ts +0 -11
- package/dist/error/jsonParseError.js +0 -14
- package/dist-esm/error/app.error.js +0 -53
- package/dist-esm/error/httpRequestError.js +0 -28
- package/dist-esm/error/jsonParseError.js +0 -10
- package/src/error/app.error.ts +0 -81
- package/src/error/httpRequestError.ts +0 -38
- package/src/error/jsonParseError.ts +0 -20
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _jsonParseIfPossible, _stringifyAny, _truncate, _truncateMiddle } 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
|
}
|
|
@@ -159,3 +207,112 @@ export function _errorDataAppend(err, data) {
|
|
|
159
207
|
err.data = Object.assign(Object.assign({}, err.data), data);
|
|
160
208
|
return err;
|
|
161
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Base class for all our (not system) errors.
|
|
212
|
+
*
|
|
213
|
+
* message - "technical" message. Frontend decides to show it or not.
|
|
214
|
+
* data - optional "any" payload.
|
|
215
|
+
* data.userFriendly - if present, will be displayed to the User as is.
|
|
216
|
+
*
|
|
217
|
+
* Based on: https://medium.com/@xpl/javascript-deriving-from-error-properly-8d2f8f315801
|
|
218
|
+
*/
|
|
219
|
+
export class AppError extends Error {
|
|
220
|
+
constructor(message, data = {}, opt = {}) {
|
|
221
|
+
super(message);
|
|
222
|
+
const { name = this.constructor.name, cause } = opt;
|
|
223
|
+
Object.defineProperties(this, {
|
|
224
|
+
name: {
|
|
225
|
+
value: name,
|
|
226
|
+
configurable: true,
|
|
227
|
+
writable: true,
|
|
228
|
+
},
|
|
229
|
+
data: {
|
|
230
|
+
value: data,
|
|
231
|
+
writable: true,
|
|
232
|
+
configurable: true,
|
|
233
|
+
enumerable: false,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
if (cause) {
|
|
237
|
+
Object.defineProperty(this, 'cause', {
|
|
238
|
+
value: _anyToErrorObject(cause),
|
|
239
|
+
writable: true,
|
|
240
|
+
configurable: true,
|
|
241
|
+
enumerable: true, // unlike standard - setting it to true for "visibility"
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// this is to allow changing this.constuctor.name to a non-minified version
|
|
245
|
+
Object.defineProperty(this.constructor, 'name', {
|
|
246
|
+
value: name,
|
|
247
|
+
configurable: true,
|
|
248
|
+
writable: true,
|
|
249
|
+
});
|
|
250
|
+
// todo: check if it's needed at all!
|
|
251
|
+
// if (Error.captureStackTrace) {
|
|
252
|
+
// Error.captureStackTrace(this, this.constructor)
|
|
253
|
+
// } else {
|
|
254
|
+
// Object.defineProperty(this, 'stack', {
|
|
255
|
+
// value: new Error().stack, // eslint-disable-line unicorn/error-message
|
|
256
|
+
// writable: true,
|
|
257
|
+
// configurable: true,
|
|
258
|
+
// })
|
|
259
|
+
// }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Error that is thrown when Http Request was made and returned an error.
|
|
264
|
+
* Thrown by, for example, Fetcher.
|
|
265
|
+
*
|
|
266
|
+
* On the Frontend this Error class represents the error when calling the API,
|
|
267
|
+
* contains all the necessary request and response information.
|
|
268
|
+
*
|
|
269
|
+
* On the Backend, similarly, it represents the error when calling some 3rd-party API
|
|
270
|
+
* (backend-to-backend call).
|
|
271
|
+
* On the Backend it often propagates all the way to the Backend error handler,
|
|
272
|
+
* where it would be wrapped in BackendErrorResponseObject.
|
|
273
|
+
*
|
|
274
|
+
* Please note that `ErrorData.backendResponseStatusCode` is NOT exactly the same as
|
|
275
|
+
* `HttpRequestErrorData.responseStatusCode`.
|
|
276
|
+
* E.g 3rd-party call may return 401, but our Backend will still wrap it into an 500 error
|
|
277
|
+
* (by default).
|
|
278
|
+
*/
|
|
279
|
+
export class HttpRequestError extends AppError {
|
|
280
|
+
constructor(message, data, opt) {
|
|
281
|
+
if (data.response) {
|
|
282
|
+
Object.defineProperty(data, 'response', {
|
|
283
|
+
enumerable: false,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
super(message, data, Object.assign(Object.assign({}, opt), { name: 'HttpRequestError' }));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export class AssertionError extends AppError {
|
|
290
|
+
constructor(message, data) {
|
|
291
|
+
super(message, data, { name: 'AssertionError' });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
export class JsonParseError extends AppError {
|
|
295
|
+
constructor(data) {
|
|
296
|
+
const message = ['Failed to parse', data.text && _truncateMiddle(data.text, 200)]
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
.join(': ');
|
|
299
|
+
super(message, data, { name: 'JsonParseError' });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
export class TimeoutError extends AppError {
|
|
303
|
+
constructor(message, data, opt) {
|
|
304
|
+
super(message, data, Object.assign(Object.assign({}, opt), { name: 'TimeoutError' }));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* It is thrown when Error was expected, but didn't happen
|
|
309
|
+
* ("pass" happened instead).
|
|
310
|
+
* "Pass" means "no error".
|
|
311
|
+
*/
|
|
312
|
+
export class UnexpectedPassError extends AppError {
|
|
313
|
+
constructor() {
|
|
314
|
+
super('expected error was not thrown', {}, {
|
|
315
|
+
name: 'UnexpectedPassError',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
package/dist-esm/error/try.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _stringifyAny } from '../string/stringifyAny';
|
|
2
|
-
import { AppError } from './app.error';
|
|
3
2
|
import { _assertErrorClassOrRethrow } from './assert';
|
|
3
|
+
import { UnexpectedPassError } from './error.util';
|
|
4
4
|
/**
|
|
5
5
|
* Calls a function, returns a Tuple of [error, value].
|
|
6
6
|
* Allows to write shorter code that avoids `try/catch`.
|
|
@@ -45,18 +45,6 @@ export async function pTry(promise, errorClass) {
|
|
|
45
45
|
return [err, null];
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
49
|
-
* It is thrown when Error was expected, but didn't happen
|
|
50
|
-
* ("pass" happened instead).
|
|
51
|
-
* "Pass" means "no error".
|
|
52
|
-
*/
|
|
53
|
-
export class UnexpectedPassError extends AppError {
|
|
54
|
-
constructor() {
|
|
55
|
-
super('expected error was not thrown', {}, {
|
|
56
|
-
name: 'UnexpectedPassError',
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
48
|
/**
|
|
61
49
|
* Calls `fn`, expects is to throw, catches the expected error and returns.
|
|
62
50
|
* If error was NOT thrown - throws UnexpectedPassError instead.
|
package/dist-esm/http/fetcher.js
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
var _a;
|
|
4
4
|
import { isServerSide } from '../env';
|
|
5
5
|
import { _assertErrorClassOrRethrow } from '../error/assert';
|
|
6
|
-
import { _anyToError, _anyToErrorObject, _errorLikeToErrorObject } from '../error/error.util';
|
|
7
|
-
import { HttpRequestError } from '../error/httpRequestError';
|
|
6
|
+
import { _anyToError, _anyToErrorObject, _errorLikeToErrorObject, HttpRequestError, TimeoutError, } from '../error/error.util';
|
|
8
7
|
import { _clamp } from '../number/number.util';
|
|
9
8
|
import { _filterNullishValues, _filterUndefinedValues, _mapKeys, _merge, _omit, _pick, } from '../object/object.util';
|
|
10
9
|
import { pDelay } from '../promise/pDelay';
|
|
11
|
-
import { pTimeout
|
|
10
|
+
import { pTimeout } from '../promise/pTimeout';
|
|
12
11
|
import { _jsonParse, _jsonParseIfPossible } from '../string/json.util';
|
|
13
12
|
import { _stringifyAny } from '../string/stringifyAny';
|
|
14
13
|
import { _ms, _since } from '../time/time.util';
|
package/dist-esm/index.js
CHANGED
|
@@ -14,16 +14,13 @@ export * from './decorators/memoFn';
|
|
|
14
14
|
export * from './decorators/memoFnAsync';
|
|
15
15
|
export * from './decorators/retry.decorator';
|
|
16
16
|
export * from './decorators/timeout.decorator';
|
|
17
|
-
export * from './error/app.error';
|
|
18
17
|
export * from './error/assert';
|
|
19
18
|
export * from './enum.util';
|
|
20
19
|
export * from './error/error.model';
|
|
21
20
|
export * from './error/error.util';
|
|
22
21
|
export * from './error/errorMode';
|
|
23
|
-
export * from './error/httpRequestError';
|
|
24
22
|
export * from './error/try';
|
|
25
23
|
export * from './error/tryCatch';
|
|
26
|
-
export * from './error/jsonParseError';
|
|
27
24
|
export * from './json-schema/from-data/generateJsonSchemaFromData';
|
|
28
25
|
export * from './json-schema/jsonSchema.cnst';
|
|
29
26
|
export * from './json-schema/jsonSchema.model';
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _errorDataAppend } from '../error/error.util';
|
|
3
|
-
export class TimeoutError extends AppError {
|
|
4
|
-
constructor(message, data, opt) {
|
|
5
|
-
super(message, data, Object.assign(Object.assign({}, opt), { name: 'TimeoutError' }));
|
|
6
|
-
}
|
|
7
|
-
}
|
|
1
|
+
import { _errorDataAppend, TimeoutError } from '../error/error.util';
|
|
8
2
|
/**
|
|
9
3
|
* Decorates a Function with a timeout.
|
|
10
4
|
* Returns a decorated Function.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/js-lib",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.173.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": "^
|
|
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",
|
package/src/error/assert.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ErrorData, ErrorObject } from '..'
|
|
2
|
-
import { _deepEquals, _isErrorObject, _stringifyAny, Class } from '..'
|
|
3
|
-
import { AppError } from './app.error'
|
|
2
|
+
import { _deepEquals, _isErrorObject, _stringifyAny, AssertionError, Class } from '..'
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Evaluates the `condition` (casts it to Boolean).
|
|
@@ -150,9 +149,3 @@ export function _assertTypeOf<T>(v: any, expectedType: string, message?: string)
|
|
|
150
149
|
})
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
|
-
|
|
154
|
-
export class AssertionError extends AppError {
|
|
155
|
-
constructor(message: string, data?: ErrorData) {
|
|
156
|
-
super(message, data, { name: 'AssertionError' })
|
|
157
|
-
}
|
|
158
|
-
}
|
package/src/error/error.model.ts
CHANGED
|
@@ -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.
|
package/src/error/error.util.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
HttpRequestErrorData,
|
|
7
7
|
ErrorLike,
|
|
8
8
|
} from '..'
|
|
9
|
-
import {
|
|
9
|
+
import { _jsonParseIfPossible, _stringifyAny, _truncate, _truncateMiddle } 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
|
}
|
|
@@ -206,3 +272,165 @@ export function _errorDataAppend<ERR>(err: ERR, data?: ErrorData): ERR {
|
|
|
206
272
|
|
|
207
273
|
return err
|
|
208
274
|
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Base class for all our (not system) errors.
|
|
278
|
+
*
|
|
279
|
+
* message - "technical" message. Frontend decides to show it or not.
|
|
280
|
+
* data - optional "any" payload.
|
|
281
|
+
* data.userFriendly - if present, will be displayed to the User as is.
|
|
282
|
+
*
|
|
283
|
+
* Based on: https://medium.com/@xpl/javascript-deriving-from-error-properly-8d2f8f315801
|
|
284
|
+
*/
|
|
285
|
+
export class AppError<DATA_TYPE extends ErrorData = ErrorData> extends Error {
|
|
286
|
+
data!: DATA_TYPE
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* `cause` here is normalized to be an ErrorObject
|
|
290
|
+
*/
|
|
291
|
+
override cause?: ErrorObject
|
|
292
|
+
|
|
293
|
+
constructor(message: string, data = {} as DATA_TYPE, opt: AppErrorOptions = {}) {
|
|
294
|
+
super(message)
|
|
295
|
+
const { name = this.constructor.name, cause } = opt
|
|
296
|
+
|
|
297
|
+
Object.defineProperties(this, {
|
|
298
|
+
name: {
|
|
299
|
+
value: name,
|
|
300
|
+
configurable: true,
|
|
301
|
+
writable: true,
|
|
302
|
+
},
|
|
303
|
+
data: {
|
|
304
|
+
value: data,
|
|
305
|
+
writable: true,
|
|
306
|
+
configurable: true,
|
|
307
|
+
enumerable: false,
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
if (cause) {
|
|
312
|
+
Object.defineProperty(this, 'cause', {
|
|
313
|
+
value: _anyToErrorObject(cause),
|
|
314
|
+
writable: true,
|
|
315
|
+
configurable: true,
|
|
316
|
+
enumerable: true, // unlike standard - setting it to true for "visibility"
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// this is to allow changing this.constuctor.name to a non-minified version
|
|
321
|
+
Object.defineProperty(this.constructor, 'name', {
|
|
322
|
+
value: name,
|
|
323
|
+
configurable: true,
|
|
324
|
+
writable: true,
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
// todo: check if it's needed at all!
|
|
328
|
+
// if (Error.captureStackTrace) {
|
|
329
|
+
// Error.captureStackTrace(this, this.constructor)
|
|
330
|
+
// } else {
|
|
331
|
+
// Object.defineProperty(this, 'stack', {
|
|
332
|
+
// value: new Error().stack, // eslint-disable-line unicorn/error-message
|
|
333
|
+
// writable: true,
|
|
334
|
+
// configurable: true,
|
|
335
|
+
// })
|
|
336
|
+
// }
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Extra options for AppError constructor.
|
|
342
|
+
*/
|
|
343
|
+
export interface AppErrorOptions {
|
|
344
|
+
/**
|
|
345
|
+
* Overrides Error.name and Error.constructor.name
|
|
346
|
+
*/
|
|
347
|
+
name?: string
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Sets Error.cause.
|
|
351
|
+
* It is transformed with _anyToErrorObject()
|
|
352
|
+
*/
|
|
353
|
+
cause?: any
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Error that is thrown when Http Request was made and returned an error.
|
|
358
|
+
* Thrown by, for example, Fetcher.
|
|
359
|
+
*
|
|
360
|
+
* On the Frontend this Error class represents the error when calling the API,
|
|
361
|
+
* contains all the necessary request and response information.
|
|
362
|
+
*
|
|
363
|
+
* On the Backend, similarly, it represents the error when calling some 3rd-party API
|
|
364
|
+
* (backend-to-backend call).
|
|
365
|
+
* On the Backend it often propagates all the way to the Backend error handler,
|
|
366
|
+
* where it would be wrapped in BackendErrorResponseObject.
|
|
367
|
+
*
|
|
368
|
+
* Please note that `ErrorData.backendResponseStatusCode` is NOT exactly the same as
|
|
369
|
+
* `HttpRequestErrorData.responseStatusCode`.
|
|
370
|
+
* E.g 3rd-party call may return 401, but our Backend will still wrap it into an 500 error
|
|
371
|
+
* (by default).
|
|
372
|
+
*/
|
|
373
|
+
export class HttpRequestError extends AppError<HttpRequestErrorData> {
|
|
374
|
+
constructor(message: string, data: HttpRequestErrorData, opt?: AppErrorOptions) {
|
|
375
|
+
if (data.response) {
|
|
376
|
+
Object.defineProperty(data, 'response', {
|
|
377
|
+
enumerable: false,
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
super(message, data, { ...opt, name: 'HttpRequestError' })
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Cause is strictly-defined for HttpRequestError,
|
|
386
|
+
* so it always has a cause.
|
|
387
|
+
* (for dev convenience)
|
|
388
|
+
*/
|
|
389
|
+
override cause!: ErrorObject
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export class AssertionError extends AppError {
|
|
393
|
+
constructor(message: string, data?: ErrorData) {
|
|
394
|
+
super(message, data, { name: 'AssertionError' })
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export interface JsonParseErrorData extends ErrorData {
|
|
399
|
+
/**
|
|
400
|
+
* Original text that failed to get parsed.
|
|
401
|
+
*/
|
|
402
|
+
text?: string
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export class JsonParseError extends AppError<JsonParseErrorData> {
|
|
406
|
+
constructor(data: JsonParseErrorData) {
|
|
407
|
+
const message = ['Failed to parse', data.text && _truncateMiddle(data.text, 200)]
|
|
408
|
+
.filter(Boolean)
|
|
409
|
+
.join(': ')
|
|
410
|
+
|
|
411
|
+
super(message, data, { name: 'JsonParseError' })
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export class TimeoutError extends AppError {
|
|
416
|
+
constructor(message: string, data?: ErrorData, opt?: AppErrorOptions) {
|
|
417
|
+
super(message, data, { ...opt, name: 'TimeoutError' })
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* It is thrown when Error was expected, but didn't happen
|
|
423
|
+
* ("pass" happened instead).
|
|
424
|
+
* "Pass" means "no error".
|
|
425
|
+
*/
|
|
426
|
+
export class UnexpectedPassError extends AppError {
|
|
427
|
+
constructor() {
|
|
428
|
+
super(
|
|
429
|
+
'expected error was not thrown',
|
|
430
|
+
{},
|
|
431
|
+
{
|
|
432
|
+
name: 'UnexpectedPassError',
|
|
433
|
+
},
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
}
|
package/src/error/try.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { _stringifyAny } from '../string/stringifyAny'
|
|
2
2
|
import type { Class } from '../typeFest'
|
|
3
3
|
import type { AnyFunction, ErrorDataTuple } from '../types'
|
|
4
|
-
import { AppError } from './app.error'
|
|
5
4
|
import { _assertErrorClassOrRethrow } from './assert'
|
|
5
|
+
import { UnexpectedPassError } from './error.util'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Calls a function, returns a Tuple of [error, value].
|
|
@@ -55,23 +55,6 @@ export async function pTry<T, ERR extends Error = Error>(
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
59
|
-
* It is thrown when Error was expected, but didn't happen
|
|
60
|
-
* ("pass" happened instead).
|
|
61
|
-
* "Pass" means "no error".
|
|
62
|
-
*/
|
|
63
|
-
export class UnexpectedPassError extends AppError {
|
|
64
|
-
constructor() {
|
|
65
|
-
super(
|
|
66
|
-
'expected error was not thrown',
|
|
67
|
-
{},
|
|
68
|
-
{
|
|
69
|
-
name: 'UnexpectedPassError',
|
|
70
|
-
},
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
58
|
/**
|
|
76
59
|
* Calls `fn`, expects is to throw, catches the expected error and returns.
|
|
77
60
|
* If error was NOT thrown - throws UnexpectedPassError instead.
|
package/src/http/fetcher.ts
CHANGED
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
import { isServerSide } from '../env'
|
|
5
5
|
import { _assertErrorClassOrRethrow } from '../error/assert'
|
|
6
6
|
import { ErrorLike, ErrorObject } from '../error/error.model'
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
_anyToError,
|
|
9
|
+
_anyToErrorObject,
|
|
10
|
+
_errorLikeToErrorObject,
|
|
11
|
+
HttpRequestError,
|
|
12
|
+
TimeoutError,
|
|
13
|
+
} from '../error/error.util'
|
|
9
14
|
import { _clamp } from '../number/number.util'
|
|
10
15
|
import {
|
|
11
16
|
_filterNullishValues,
|
|
@@ -16,7 +21,7 @@ import {
|
|
|
16
21
|
_pick,
|
|
17
22
|
} from '../object/object.util'
|
|
18
23
|
import { pDelay } from '../promise/pDelay'
|
|
19
|
-
import { pTimeout
|
|
24
|
+
import { pTimeout } from '../promise/pTimeout'
|
|
20
25
|
import { _jsonParse, _jsonParseIfPossible } from '../string/json.util'
|
|
21
26
|
import { _stringifyAny } from '../string/stringifyAny'
|
|
22
27
|
import { _ms, _since } from '../time/time.util'
|
package/src/index.ts
CHANGED
|
@@ -14,16 +14,13 @@ export * from './decorators/memoFn'
|
|
|
14
14
|
export * from './decorators/memoFnAsync'
|
|
15
15
|
export * from './decorators/retry.decorator'
|
|
16
16
|
export * from './decorators/timeout.decorator'
|
|
17
|
-
export * from './error/app.error'
|
|
18
17
|
export * from './error/assert'
|
|
19
18
|
export * from './enum.util'
|
|
20
19
|
export * from './error/error.model'
|
|
21
20
|
export * from './error/error.util'
|
|
22
21
|
export * from './error/errorMode'
|
|
23
|
-
export * from './error/httpRequestError'
|
|
24
22
|
export * from './error/try'
|
|
25
23
|
export * from './error/tryCatch'
|
|
26
|
-
export * from './error/jsonParseError'
|
|
27
24
|
export * from './json-schema/from-data/generateJsonSchemaFromData'
|
|
28
25
|
export * from './json-schema/jsonSchema.cnst'
|
|
29
26
|
export * from './json-schema/jsonSchema.model'
|