@naturalcycles/js-lib 14.141.0 → 14.143.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 (52) hide show
  1. package/dist/error/app.error.d.ts +4 -4
  2. package/dist/error/app.error.js +3 -3
  3. package/dist/error/assert.d.ts +5 -3
  4. package/dist/error/assert.js +18 -6
  5. package/dist/error/error.model.d.ts +44 -21
  6. package/dist/error/error.util.d.ts +4 -4
  7. package/dist/error/error.util.js +8 -11
  8. package/dist/error/httpRequestError.d.ts +22 -0
  9. package/dist/error/httpRequestError.js +27 -0
  10. package/dist/error/jsonParseError.d.ts +11 -0
  11. package/dist/error/jsonParseError.js +11 -0
  12. package/dist/error/try.js +1 -1
  13. package/dist/http/fetcher.js +34 -31
  14. package/dist/http/http.model.d.ts +4 -0
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +2 -1
  17. package/dist/promise/pTimeout.d.ts +2 -2
  18. package/dist/promise/pTimeout.js +2 -2
  19. package/dist/string/json.util.d.ts +9 -1
  20. package/dist/string/json.util.js +19 -1
  21. package/dist/string/stringifyAny.js +6 -6
  22. package/dist/types.d.ts +8 -0
  23. package/dist-esm/error/app.error.js +3 -3
  24. package/dist-esm/error/assert.js +17 -6
  25. package/dist-esm/error/error.util.js +5 -8
  26. package/dist-esm/error/httpRequestError.js +23 -0
  27. package/dist-esm/error/jsonParseError.js +7 -0
  28. package/dist-esm/error/try.js +1 -1
  29. package/dist-esm/http/fetcher.js +36 -28
  30. package/dist-esm/index.js +2 -1
  31. package/dist-esm/promise/pTimeout.js +2 -2
  32. package/dist-esm/string/json.util.js +17 -0
  33. package/dist-esm/string/stringifyAny.js +7 -7
  34. package/package.json +1 -1
  35. package/src/error/app.error.ts +6 -6
  36. package/src/error/assert.ts +22 -9
  37. package/src/error/error.model.ts +48 -23
  38. package/src/error/error.util.ts +15 -12
  39. package/src/error/httpRequestError.ts +25 -0
  40. package/src/error/jsonParseError.ts +21 -0
  41. package/src/error/try.ts +1 -1
  42. package/src/http/fetcher.ts +42 -32
  43. package/src/http/http.model.ts +5 -0
  44. package/src/index.ts +2 -1
  45. package/src/promise/pTimeout.ts +3 -3
  46. package/src/string/json.util.ts +20 -4
  47. package/src/string/stringifyAny.ts +7 -7
  48. package/src/types.ts +11 -0
  49. package/dist/error/http.error.d.ts +0 -8
  50. package/dist/error/http.error.js +0 -13
  51. package/dist-esm/error/http.error.js +0 -9
  52. package/src/error/http.error.ts +0 -13
@@ -8,7 +8,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 = {}, opt, name) {
11
+ constructor(message, data = {}, cause, name) {
12
12
  super(message);
13
13
  Object.defineProperty(this, 'name', {
14
14
  value: name || this.constructor.name,
@@ -27,10 +27,10 @@ export class AppError extends Error {
27
27
  configurable: true,
28
28
  enumerable: false,
29
29
  });
30
- if (opt === null || opt === void 0 ? void 0 : opt.cause) {
30
+ if (cause) {
31
31
  Object.defineProperty(this, 'cause', {
32
32
  // I'd love to do _anyToError(opt.cause) here, but it causes circular dep ;(
33
- value: opt.cause,
33
+ value: cause,
34
34
  writable: true,
35
35
  configurable: true,
36
36
  enumerable: false,
@@ -1,4 +1,4 @@
1
- import { _deepEquals, _stringifyAny } from '..';
1
+ import { _deepEquals, _isErrorObject, _stringifyAny } from '..';
2
2
  import { AppError } from './app.error';
3
3
  /**
4
4
  * Evaluates the `condition` (casts it to Boolean).
@@ -57,9 +57,20 @@ export function _assertDeepEquals(actual, expected, message, errorData) {
57
57
  throw new AssertionError(msg, Object.assign({ userFriendly: true }, errorData));
58
58
  }
59
59
  }
60
- export function _assertIsError(err, message) {
61
- if (!(err instanceof Error)) {
62
- const msg = [message || `expected to be instanceof Error`, `actual typeof: ${typeof err}`].join('\n');
60
+ export function _assertIsError(err, errorClass = Error) {
61
+ if (!(err instanceof errorClass)) {
62
+ const msg = [
63
+ `expected to be instanceof ${errorClass.name}`,
64
+ `actual typeof: ${typeof err}`,
65
+ ].join('\n');
66
+ throw new AssertionError(msg, {
67
+ userFriendly: true,
68
+ });
69
+ }
70
+ }
71
+ export function _assertIsErrorObject(obj) {
72
+ if (!_isErrorObject(obj)) {
73
+ const msg = [`expected to be ErrorObject`, `actual typeof: ${typeof obj}`].join('\n');
63
74
  throw new AssertionError(msg, {
64
75
  userFriendly: true,
65
76
  });
@@ -84,7 +95,7 @@ export function _assertTypeOf(v, expectedType, message) {
84
95
  }
85
96
  }
86
97
  export class AssertionError extends AppError {
87
- constructor(message, data = {}, opt) {
88
- super(message, data, opt, 'AssertionError');
98
+ constructor(message, data = {}, cause) {
99
+ super(message, data, cause, 'AssertionError');
89
100
  }
90
101
  }
@@ -36,7 +36,7 @@ export function _anyToErrorObject(o, errorData) {
36
36
  }
37
37
  else {
38
38
  o = _jsonParseIfPossible(o);
39
- if (_isHttpErrorResponse(o)) {
39
+ if (_isBackendErrorResponseObject(o)) {
40
40
  eo = o.error;
41
41
  }
42
42
  else if (_isErrorObject(o)) {
@@ -107,15 +107,12 @@ export function _errorObjectToError(o, errorClass = Error) {
107
107
  }
108
108
  return err;
109
109
  }
110
- export function _isHttpErrorResponse(o) {
111
- return _isHttpErrorObject(o === null || o === void 0 ? void 0 : o.error);
110
+ export function _isBackendErrorResponseObject(o) {
111
+ return _isErrorObject(o === null || o === void 0 ? void 0 : o.error);
112
112
  }
113
- export function _isHttpErrorObject(o) {
113
+ export function _isHttpRequestErrorObject(o) {
114
114
  var _a;
115
- return (!!o &&
116
- typeof o.name === 'string' &&
117
- typeof o.message === 'string' &&
118
- typeof ((_a = o.data) === null || _a === void 0 ? void 0 : _a.httpStatusCode) === 'number');
115
+ return !!o && o.name === 'HttpRequestError' && typeof ((_a = o.data) === null || _a === void 0 ? void 0 : _a.requestUrl) === 'string';
119
116
  }
120
117
  /**
121
118
  * Note: any instance of AppError is also automatically an ErrorObject
@@ -0,0 +1,23 @@
1
+ import { AppError } from './app.error';
2
+ /**
3
+ * Error that is thrown when Http Request was made and returned an error.
4
+ * Thrown by, for example, Fetcher.
5
+ *
6
+ * On the Frontend this Error class represents the error when calling the API,
7
+ * contains all the necessary request and response information.
8
+ *
9
+ * On the Backend, similarly, it represents the error when calling some 3rd-party API
10
+ * (backend-to-backend call).
11
+ * On the Backend it often propagates all the way to the Backend error handler,
12
+ * where it would be wrapped in BackendErrorResponseObject.
13
+ *
14
+ * Please note that `ErrorData.backendResponseStatusCode` is NOT exactly the same as
15
+ * `HttpRequestErrorData.responseStatusCode`.
16
+ * E.g 3rd-party call may return 401, but our Backend will still wrap it into an 500 error
17
+ * (by default).
18
+ */
19
+ export class HttpRequestError extends AppError {
20
+ constructor(message, data, cause) {
21
+ super(message, data, cause, 'HttpRequestError');
22
+ }
23
+ }
@@ -0,0 +1,7 @@
1
+ import { _truncateMiddle } from '../string/string.util';
2
+ import { AppError } from './app.error';
3
+ export class JsonParseError extends AppError {
4
+ constructor(data, cause) {
5
+ super(['Failed to parse', (data === null || data === void 0 ? void 0 : data.text) && _truncateMiddle(data.text, 200)].filter(Boolean).join(': '), data, cause, 'JsonParseError');
6
+ }
7
+ }
@@ -50,7 +50,7 @@ export async function pTry(promise) {
50
50
  */
51
51
  export class UnexpectedPassError extends AppError {
52
52
  constructor() {
53
- super('expected error was not thrown');
53
+ super('expected error was not thrown', {}, undefined, 'UnexpectedPassError');
54
54
  }
55
55
  }
56
56
  /**
@@ -2,11 +2,11 @@
2
2
  import { __asyncValues } from "tslib";
3
3
  import { isServerSide } from '../env';
4
4
  import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
5
- import { HttpError } from '../error/http.error';
5
+ import { HttpRequestError } from '../error/httpRequestError';
6
6
  import { _clamp } from '../number/number.util';
7
7
  import { _filterNullishValues, _filterUndefinedValues, _mapKeys, _merge, _omit, } from '../object/object.util';
8
8
  import { pDelay } from '../promise/pDelay';
9
- import { _jsonParseIfPossible } from '../string/json.util';
9
+ import { _jsonParse, _jsonParseIfPossible } from '../string/json.util';
10
10
  import { _since } from '../time/time.util';
11
11
  import { HTTP_METHODS } from './http.model';
12
12
  const defRetryOptions = {
@@ -156,7 +156,7 @@ export class Fetcher {
156
156
  }
157
157
  else {
158
158
  // !res.ok
159
- await this.onNotOkResponse(res, timeout);
159
+ await this.onNotOkResponse(res, started, timeout);
160
160
  }
161
161
  }
162
162
  try {
@@ -190,15 +190,21 @@ export class Fetcher {
190
190
  if (text) {
191
191
  try {
192
192
  res.body = text;
193
- res.body = JSON.parse(text, req.jsonReviver);
193
+ res.body = _jsonParse(text, req.jsonReviver);
194
194
  }
195
195
  catch (err) {
196
- const { message } = _anyToError(err);
197
- res.err = new HttpError([res.signature, message].join('\n'), {
198
- httpStatusCode: 0,
199
- url: req.url,
200
- });
196
+ // Error while parsing json
197
+ // res.err = _anyToError(err, HttpRequestError, {
198
+ // requestUrl: res.req.url,
199
+ // requestBaseUrl: this.cfg.baseUrl,
200
+ // requestMethod: res.req.init.method,
201
+ // requestSignature: res.signature,
202
+ // requestDuration: Date.now() - started,
203
+ // responseStatusCode: res.fetchResponse.status,
204
+ // } satisfies HttpRequestErrorData)
205
+ res.err = _anyToError(err);
201
206
  res.ok = false;
207
+ return await this.onNotOkResponse(res, started, timeout);
202
208
  }
203
209
  }
204
210
  else {
@@ -247,33 +253,35 @@ export class Fetcher {
247
253
  async callNativeFetch(url, init) {
248
254
  return await globalThis.fetch(url, init);
249
255
  }
250
- async onNotOkResponse(res, timeout) {
256
+ async onNotOkResponse(res, started, timeout) {
251
257
  var _a, _b;
252
258
  clearTimeout(timeout);
253
- let errObj;
254
- if (res.fetchResponse) {
255
- const body = _jsonParseIfPossible(await res.fetchResponse.text());
256
- errObj = _anyToErrorObject(body);
257
- }
258
- else if (res.err) {
259
- errObj = _errorToErrorObject(res.err);
259
+ let cause;
260
+ if (res.err) {
261
+ // This is only possible on JSON.parse error (or CORS error!)
262
+ // This check should go first, to avoid calling .text() twice (which will fail)
263
+ cause = _errorToErrorObject(res.err);
260
264
  }
261
- else {
262
- errObj = {};
265
+ else if (res.fetchResponse) {
266
+ const body = _jsonParseIfPossible(await res.fetchResponse.text());
267
+ if (body) {
268
+ cause = _anyToErrorObject(body);
269
+ }
263
270
  }
264
- const originalMessage = errObj.message;
265
- errObj.message = [
266
- [(_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status, res.signature].filter(Boolean).join(' '),
267
- originalMessage,
268
- ]
269
- .filter(Boolean)
270
- .join('\n');
271
- res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_b = res.fetchResponse) === null || _b === void 0 ? void 0 : _b.status) || 0,
271
+ const message = [(_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status, res.signature].filter(Boolean).join(' ');
272
+ res.err = new HttpRequestError(message, _filterNullishValues({
273
+ responseStatusCode: ((_b = res.fetchResponse) === null || _b === void 0 ? void 0 : _b.status) || 0,
272
274
  // These properties are provided to be used in e.g custom Sentry error grouping
273
275
  // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
274
276
  // Enabled, cause `data` is not printed by default when error is HttpError
275
277
  // method: req.method,
276
- url: res.req.url })));
278
+ // tryCount: req.tryCount,
279
+ requestUrl: res.req.url,
280
+ requestBaseUrl: this.cfg.baseUrl || null,
281
+ requestMethod: res.req.init.method,
282
+ requestSignature: res.signature,
283
+ requestDuration: Date.now() - started,
284
+ }), cause);
277
285
  await this.processRetry(res);
278
286
  }
279
287
  async processRetry(res) {
package/dist-esm/index.js CHANGED
@@ -20,9 +20,10 @@ export * from './enum.util';
20
20
  export * from './error/error.model';
21
21
  export * from './error/error.util';
22
22
  export * from './error/errorMode';
23
- export * from './error/http.error';
23
+ export * from './error/httpRequestError';
24
24
  export * from './error/try';
25
25
  export * from './error/tryCatch';
26
+ export * from './error/jsonParseError';
26
27
  export * from './json-schema/from-data/generateJsonSchemaFromData';
27
28
  export * from './json-schema/jsonSchema.cnst';
28
29
  export * from './json-schema/jsonSchema.model';
@@ -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 = {}, opt) {
5
- super(message, data, opt, 'TimeoutError');
4
+ constructor(message, data = {}, cause) {
5
+ super(message, data, cause, 'TimeoutError');
6
6
  }
7
7
  }
8
8
  /**
@@ -1,3 +1,4 @@
1
+ import { JsonParseError } from '../error/jsonParseError';
1
2
  // const possibleJsonStartTokens = ['{', '[', '"']
2
3
  const DETECT_JSON = /^\s*[{["\-\d]/;
3
4
  /**
@@ -14,3 +15,19 @@ export function _jsonParseIfPossible(obj, reviver) {
14
15
  }
15
16
  return obj;
16
17
  }
18
+ /**
19
+ * Same as JSON.parse, but throws JsonParseError:
20
+ *
21
+ * 1. It's message includes a piece of source text (truncated)
22
+ * 2. It's data.text contains full source text
23
+ */
24
+ export function _jsonParse(s, reviver) {
25
+ try {
26
+ return JSON.parse(s, reviver);
27
+ }
28
+ catch (_a) {
29
+ throw new JsonParseError({
30
+ text: s,
31
+ });
32
+ }
33
+ }
@@ -1,4 +1,4 @@
1
- import { _isErrorObject, _isHttpErrorObject, _isHttpErrorResponse } from '../error/error.util';
1
+ import { _isErrorObject, _isBackendErrorResponseObject } 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';
@@ -52,7 +52,7 @@ export function _stringifyAny(obj, opt = {}) {
52
52
  //
53
53
  // HttpErrorResponse
54
54
  //
55
- if (_isHttpErrorResponse(obj)) {
55
+ if (_isBackendErrorResponseObject(obj)) {
56
56
  return _stringifyAny(obj.error, opt);
57
57
  }
58
58
  if (obj instanceof Error || _isErrorObject(obj)) {
@@ -65,11 +65,11 @@ export function _stringifyAny(obj, opt = {}) {
65
65
  // if (obj?.name === 'Error') {
66
66
  // s = obj.message
67
67
  // }
68
- if (_isErrorObject(obj) && _isHttpErrorObject(obj)) {
69
- // Printing (0) to avoid ambiguity
70
- s = `${obj.name}(${obj.data.httpStatusCode}): ${obj.message}`;
71
- }
72
- s || (s = [obj.name, obj.message].filter(Boolean).join(': '));
68
+ // if (_isErrorObject(obj) && _isHttpErrorObject(obj)) {
69
+ // // Printing (0) to avoid ambiguity
70
+ // s = `${obj.name}(${obj.data.httpStatusCode}): ${obj.message}`
71
+ // }
72
+ s = [obj.name, obj.message].filter(Boolean).join(': ');
73
73
  if (typeof obj.code === 'string') {
74
74
  // Error that has no `data`, but has `code` property
75
75
  s += `\ncode: ${obj.code}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.141.0",
3
+ "version": "14.143.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -1,4 +1,4 @@
1
- import type { ErrorData } from './error.model'
1
+ import type { ErrorData, ErrorObject } from './error.model'
2
2
 
3
3
  /**
4
4
  * Base class for all our (not system) errors.
@@ -13,11 +13,11 @@ 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 instance of Error
16
+ * cause here is normalized to be an ErrorObject
17
17
  */
18
- override cause?: Error
18
+ override cause?: ErrorObject
19
19
 
20
- constructor(message: string, data = {} as DATA_TYPE, opt?: ErrorOptions, name?: string) {
20
+ constructor(message: string, data = {} as DATA_TYPE, cause?: ErrorObject, name?: string) {
21
21
  super(message)
22
22
 
23
23
  Object.defineProperty(this, 'name', {
@@ -40,10 +40,10 @@ export class AppError<DATA_TYPE extends ErrorData = ErrorData> extends Error {
40
40
  enumerable: false,
41
41
  })
42
42
 
43
- if (opt?.cause) {
43
+ if (cause) {
44
44
  Object.defineProperty(this, 'cause', {
45
45
  // I'd love to do _anyToError(opt.cause) here, but it causes circular dep ;(
46
- value: opt.cause,
46
+ value: cause,
47
47
  writable: true,
48
48
  configurable: true,
49
49
  enumerable: false,
@@ -1,5 +1,5 @@
1
- import type { ErrorData } from '..'
2
- import { _deepEquals, _stringifyAny } from '..'
1
+ import type { ErrorData, ErrorObject } from '..'
2
+ import { _deepEquals, _isErrorObject, _stringifyAny, Class } from '..'
3
3
  import { AppError } from './app.error'
4
4
 
5
5
  /**
@@ -88,12 +88,25 @@ export function _assertDeepEquals<T>(
88
88
 
89
89
  export function _assertIsError<ERR extends Error = Error>(
90
90
  err: any,
91
- message?: string,
91
+ errorClass: Class<ERR> = Error as any,
92
92
  ): asserts err is ERR {
93
- if (!(err instanceof Error)) {
94
- const msg = [message || `expected to be instanceof Error`, `actual typeof: ${typeof err}`].join(
95
- '\n',
96
- )
93
+ if (!(err instanceof errorClass)) {
94
+ const msg = [
95
+ `expected to be instanceof ${errorClass.name}`,
96
+ `actual typeof: ${typeof err}`,
97
+ ].join('\n')
98
+
99
+ throw new AssertionError(msg, {
100
+ userFriendly: true,
101
+ })
102
+ }
103
+ }
104
+
105
+ export function _assertIsErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
106
+ obj: any,
107
+ ): asserts obj is ErrorObject<DATA_TYPE> {
108
+ if (!_isErrorObject(obj)) {
109
+ const msg = [`expected to be ErrorObject`, `actual typeof: ${typeof obj}`].join('\n')
97
110
 
98
111
  throw new AssertionError(msg, {
99
112
  userFriendly: true,
@@ -124,7 +137,7 @@ export function _assertTypeOf<T>(v: any, expectedType: string, message?: string)
124
137
  }
125
138
 
126
139
  export class AssertionError extends AppError {
127
- constructor(message: string, data = {}, opt?: ErrorOptions) {
128
- super(message, data, opt, 'AssertionError')
140
+ constructor(message: string, data = {}, cause?: ErrorObject) {
141
+ super(message, data, cause, 'AssertionError')
129
142
  }
130
143
  }
@@ -1,3 +1,6 @@
1
+ import { HttpMethod, HttpStatusCode } from '../http/http.model'
2
+ import { NumberOfMilliseconds } from '../types'
3
+
1
4
  /**
2
5
  * Extendable payload object to transfer custom additional data with AppError.
3
6
  */
@@ -21,9 +24,17 @@ export interface ErrorData {
21
24
  /**
22
25
  * Set to true to force reporting this error (e.g to Sentry).
23
26
  * Useful to be able to force-report e.g a 4xx error, which by default wouldn't be reported.
27
+ * Set to false to force not-reporting it.
24
28
  */
25
29
  report?: boolean
26
30
 
31
+ /**
32
+ * Set to true or false to control error reporting on the Client-side.
33
+ * It works as in indication/hint, not a guarantee,
34
+ * because Client-side still need to manually check and respect this property.
35
+ */
36
+ reportClientSide?: boolean
37
+
27
38
  /**
28
39
  * If defined - used by SentrySharedService in backend-lib.
29
40
  * Allows to report only X% of errors of this type.
@@ -36,7 +47,8 @@ export interface ErrorData {
36
47
  * (e.g frontend-lib adds a method, url, etc for all the errors)
37
48
  * `originalMessage` is used to preserve the original `error.message` as it came from the backend.
38
49
  */
39
- originalMessage?: string
50
+ // originalMessage?: string
51
+ // use .cause.message instead
40
52
 
41
53
  /**
42
54
  * Can be used by error-reporting tools (e.g Sentry).
@@ -45,42 +57,51 @@ export interface ErrorData {
45
57
  */
46
58
  fingerprint?: string[]
47
59
 
48
- httpStatusCode?: number
49
-
50
60
  /**
51
- * Open-ended.
61
+ * Set when throwing an error from your backend code, to indicate desired http status code.
62
+ * e.g throw new AppError('oj', { backendResponseStatusCode: 401 })
52
63
  */
53
- [k: string]: any
54
- }
64
+ backendResponseStatusCode?: HttpStatusCode
55
65
 
56
- export interface HttpErrorData extends ErrorData {
57
66
  /**
58
- * @default 500
67
+ * Set to true when the error was thrown after response headers were sent.
59
68
  */
60
- httpStatusCode: number
69
+ headersSent?: boolean
61
70
 
62
71
  /**
63
- * @example
64
- *
65
- * GET /api/some-endpoint
72
+ * Used in e.g http 401 error.
66
73
  */
67
- // endpoint?: string
74
+ adminAuthRequired?: boolean
68
75
 
69
76
  /**
70
- * Set to true when the error was thrown after response headers were sent.
77
+ * Used in e.g http 403 error.
71
78
  */
72
- headersSent?: boolean
73
- }
79
+ adminPermissionsRequired?: string[]
74
80
 
75
- export interface Admin401ErrorData extends HttpErrorData {
76
- adminAuthRequired: true
81
+ /**
82
+ * Open-ended.
83
+ */
84
+ [k: string]: any
77
85
  }
78
86
 
79
- export interface Admin403ErrorData extends HttpErrorData {
87
+ export interface HttpRequestErrorData extends ErrorData {
88
+ requestUrl: string
89
+ requestBaseUrl?: string
90
+ requestMethod: HttpMethod
80
91
  /**
81
- * Returns non-empty array.
92
+ * Conveniently combines Method and Url, respects `logWithSearchParams` (and similar) configuration.
93
+ * E.g:
94
+ * GET /some/url
82
95
  */
83
- adminPermissionsRequired: string[]
96
+ requestSignature: string
97
+ /**
98
+ * Can be set to 0 if request "failed to start" or "failed to reach the server".
99
+ */
100
+ requestDuration: NumberOfMilliseconds
101
+ /**
102
+ * 0 is used for edge cases when e.g it failed to reach the server.
103
+ */
104
+ responseStatusCode: HttpStatusCode
84
105
  }
85
106
 
86
107
  // export interface ErrorLike {
@@ -101,6 +122,8 @@ export interface Admin403ErrorData extends HttpErrorData {
101
122
  * Cannot contain any properties that stock Error doesn't have, otherwise
102
123
  * it will be hard to transform it to ErrorObject.
103
124
  * Everything "extra" should go under `data`.
125
+ *
126
+ * Instance of AppError class should satisfy ErrorObject interface.
104
127
  */
105
128
  export interface ErrorObject<DATA_TYPE extends ErrorData = ErrorData> {
106
129
  /**
@@ -134,8 +157,10 @@ export interface ErrorObject<DATA_TYPE extends ErrorData = ErrorData> {
134
157
  }
135
158
 
136
159
  /**
137
- * JSON HTTP response from the Backend that represents "Error".
160
+ * JSON HTTP response object from the Backend that represents "Error".
161
+ * Assumption is that if JSON response "looks like this" - it's safe to print it in a custom way.
162
+ * Otherwise - it'll be printed in a generic way.
138
163
  */
139
- export interface HttpErrorResponse<DATA_TYPE extends HttpErrorData = HttpErrorData> {
164
+ export interface BackendErrorResponseObject<DATA_TYPE extends ErrorData = ErrorData> {
140
165
  error: ErrorObject<DATA_TYPE>
141
166
  }
@@ -1,4 +1,10 @@
1
- import type { ErrorData, ErrorObject, HttpErrorData, HttpErrorResponse, Class } from '..'
1
+ import type {
2
+ ErrorData,
3
+ ErrorObject,
4
+ BackendErrorResponseObject,
5
+ Class,
6
+ HttpRequestErrorData,
7
+ } from '..'
2
8
  import { AppError, _jsonParseIfPossible, _stringifyAny } from '..'
3
9
 
4
10
  /**
@@ -52,7 +58,7 @@ export function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
52
58
  } else {
53
59
  o = _jsonParseIfPossible(o)
54
60
 
55
- if (_isHttpErrorResponse(o)) {
61
+ if (_isBackendErrorResponseObject(o)) {
56
62
  eo = o.error as ErrorObject<DATA_TYPE>
57
63
  } else if (_isErrorObject(o)) {
58
64
  eo = o as ErrorObject<DATA_TYPE>
@@ -139,23 +145,20 @@ export function _errorObjectToError<DATA_TYPE extends ErrorData, ERROR_TYPE exte
139
145
  return err
140
146
  }
141
147
 
142
- export function _isHttpErrorResponse(o: any): o is HttpErrorResponse {
143
- return _isHttpErrorObject(o?.error)
148
+ export function _isBackendErrorResponseObject(o: any): o is BackendErrorResponseObject {
149
+ return _isErrorObject(o?.error)
144
150
  }
145
151
 
146
- export function _isHttpErrorObject(o: any): o is ErrorObject<HttpErrorData> {
147
- return (
148
- !!o &&
149
- typeof o.name === 'string' &&
150
- typeof o.message === 'string' &&
151
- typeof o.data?.httpStatusCode === 'number'
152
- )
152
+ export function _isHttpRequestErrorObject(o: any): o is ErrorObject<HttpRequestErrorData> {
153
+ return !!o && o.name === 'HttpRequestError' && typeof o.data?.requestUrl === 'string'
153
154
  }
154
155
 
155
156
  /**
156
157
  * Note: any instance of AppError is also automatically an ErrorObject
157
158
  */
158
- export function _isErrorObject(o: any): o is ErrorObject {
159
+ export function _isErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
160
+ o: any,
161
+ ): o is ErrorObject<DATA_TYPE> {
159
162
  return (
160
163
  !!o &&
161
164
  typeof o === 'object' &&
@@ -0,0 +1,25 @@
1
+ import { AppError } from './app.error'
2
+ import type { ErrorObject, HttpRequestErrorData } from './error.model'
3
+
4
+ /**
5
+ * Error that is thrown when Http Request was made and returned an error.
6
+ * Thrown by, for example, Fetcher.
7
+ *
8
+ * On the Frontend this Error class represents the error when calling the API,
9
+ * contains all the necessary request and response information.
10
+ *
11
+ * On the Backend, similarly, it represents the error when calling some 3rd-party API
12
+ * (backend-to-backend call).
13
+ * On the Backend it often propagates all the way to the Backend error handler,
14
+ * where it would be wrapped in BackendErrorResponseObject.
15
+ *
16
+ * Please note that `ErrorData.backendResponseStatusCode` is NOT exactly the same as
17
+ * `HttpRequestErrorData.responseStatusCode`.
18
+ * E.g 3rd-party call may return 401, but our Backend will still wrap it into an 500 error
19
+ * (by default).
20
+ */
21
+ export class HttpRequestError extends AppError<HttpRequestErrorData> {
22
+ constructor(message: string, data: HttpRequestErrorData, cause?: ErrorObject) {
23
+ super(message, data, cause, 'HttpRequestError')
24
+ }
25
+ }
@@ -0,0 +1,21 @@
1
+ import { _truncateMiddle } from '../string/string.util'
2
+ import { AppError } from './app.error'
3
+ import { ErrorData, ErrorObject } from './error.model'
4
+
5
+ export interface JsonParseErrorData extends ErrorData {
6
+ /**
7
+ * Original text that failed to get parsed.
8
+ */
9
+ text?: string
10
+ }
11
+
12
+ export class JsonParseError extends AppError<JsonParseErrorData> {
13
+ constructor(data: JsonParseErrorData, cause?: ErrorObject) {
14
+ super(
15
+ ['Failed to parse', data?.text && _truncateMiddle(data.text, 200)].filter(Boolean).join(': '),
16
+ data,
17
+ cause,
18
+ 'JsonParseError',
19
+ )
20
+ }
21
+ }
package/src/error/try.ts CHANGED
@@ -57,7 +57,7 @@ export async function pTry<ERR = Error, RETURN = void>(
57
57
  */
58
58
  export class UnexpectedPassError extends AppError {
59
59
  constructor() {
60
- super('expected error was not thrown')
60
+ super('expected error was not thrown', {}, undefined, 'UnexpectedPassError')
61
61
  }
62
62
  }
63
63