@naturalcycles/js-lib 14.123.2 → 14.124.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/error.util.d.ts +2 -2
- package/dist/error/error.util.js +31 -23
- package/dist/http/fetcher.d.ts +15 -12
- package/dist/http/fetcher.js +33 -10
- package/dist-esm/error/error.util.js +28 -20
- package/dist-esm/http/fetcher.js +35 -13
- package/package.json +1 -1
- package/src/error/error.util.ts +32 -27
- package/src/http/fetcher.ts +55 -29
|
@@ -8,14 +8,14 @@ import { AppError } from '..';
|
|
|
8
8
|
*
|
|
9
9
|
* Alternatively, if you're sure it's Error - you can use `_assertIsError(err)`.
|
|
10
10
|
*/
|
|
11
|
-
export declare function _anyToError<ERROR_TYPE extends Error = Error>(o: any, errorClass?: Class<ERROR_TYPE>, opt?: StringifyAnyOptions): ERROR_TYPE;
|
|
11
|
+
export declare function _anyToError<ERROR_TYPE extends Error = Error>(o: any, errorClass?: Class<ERROR_TYPE>, errorData?: ErrorData, opt?: StringifyAnyOptions): ERROR_TYPE;
|
|
12
12
|
/**
|
|
13
13
|
* Converts "anything" to ErrorObject.
|
|
14
14
|
* Detects if it's HttpErrorResponse, HttpErrorObject, ErrorObject, Error, etc..
|
|
15
15
|
* If object is Error - Error.message will be used.
|
|
16
16
|
* Objects (not Errors) get converted to prettified JSON string (via `_stringifyAny`).
|
|
17
17
|
*/
|
|
18
|
-
export declare function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(o: any, opt?: StringifyAnyOptions): ErrorObject<DATA_TYPE>;
|
|
18
|
+
export declare function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(o: any, errorData?: Partial<DATA_TYPE>, opt?: StringifyAnyOptions): ErrorObject<DATA_TYPE>;
|
|
19
19
|
export declare function _errorToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(e: AppError<DATA_TYPE> | Error, includeErrorStack?: boolean): ErrorObject<DATA_TYPE>;
|
|
20
20
|
export declare function _errorObjectToAppError<DATA_TYPE extends ErrorData>(o: ErrorObject<DATA_TYPE>): AppError<DATA_TYPE>;
|
|
21
21
|
export declare function _errorObjectToError<DATA_TYPE extends ErrorData, ERROR_TYPE extends Error>(o: ErrorObject<DATA_TYPE>, errorClass?: Class<ERROR_TYPE>): ERROR_TYPE;
|
package/dist/error/error.util.js
CHANGED
|
@@ -10,11 +10,12 @@ const __1 = require("..");
|
|
|
10
10
|
*
|
|
11
11
|
* Alternatively, if you're sure it's Error - you can use `_assertIsError(err)`.
|
|
12
12
|
*/
|
|
13
|
-
function _anyToError(o, errorClass = Error, opt) {
|
|
13
|
+
function _anyToError(o, errorClass = Error, errorData, opt) {
|
|
14
14
|
if (o instanceof errorClass)
|
|
15
15
|
return o;
|
|
16
16
|
// If it's an instance of Error, but ErrorClass is something else (e.g AppError) - it'll be "repacked" into AppError
|
|
17
|
-
const errorObject = _isErrorObject(o) ? o : _anyToErrorObject(o, opt);
|
|
17
|
+
const errorObject = _isErrorObject(o) ? o : _anyToErrorObject(o, {}, opt);
|
|
18
|
+
Object.assign(errorObject.data, errorData);
|
|
18
19
|
return _errorObjectToError(errorObject, errorClass);
|
|
19
20
|
}
|
|
20
21
|
exports._anyToError = _anyToError;
|
|
@@ -24,30 +25,37 @@ exports._anyToError = _anyToError;
|
|
|
24
25
|
* If object is Error - Error.message will be used.
|
|
25
26
|
* Objects (not Errors) get converted to prettified JSON string (via `_stringifyAny`).
|
|
26
27
|
*/
|
|
27
|
-
function _anyToErrorObject(o, opt) {
|
|
28
|
+
function _anyToErrorObject(o, errorData, opt) {
|
|
29
|
+
let eo;
|
|
28
30
|
if (o instanceof Error) {
|
|
29
|
-
|
|
31
|
+
eo = _errorToErrorObject(o, opt?.includeErrorStack ?? true);
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
else {
|
|
34
|
+
o = (0, __1._jsonParseIfPossible)(o);
|
|
35
|
+
if (_isHttpErrorResponse(o)) {
|
|
36
|
+
eo = o.error;
|
|
37
|
+
}
|
|
38
|
+
else if (_isErrorObject(o)) {
|
|
39
|
+
eo = o;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Here we are sure it has no `data` property,
|
|
43
|
+
// so, fair to return `data: {}` in the end
|
|
44
|
+
// Also we're sure it includes no "error name", e.g no `Error: ...`,
|
|
45
|
+
// so, fair to include `name: 'Error'`
|
|
46
|
+
const message = (0, __1._stringifyAny)(o, {
|
|
47
|
+
includeErrorData: true,
|
|
48
|
+
...opt,
|
|
49
|
+
});
|
|
50
|
+
eo = {
|
|
51
|
+
name: 'Error',
|
|
52
|
+
message,
|
|
53
|
+
data: {}, // empty
|
|
54
|
+
};
|
|
55
|
+
}
|
|
34
56
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
// Here we are sure it has no `data` property,
|
|
39
|
-
// so, fair to return `data: {}` in the end
|
|
40
|
-
// Also we're sure it includes no "error name", e.g no `Error: ...`,
|
|
41
|
-
// so, fair to include `name: 'Error'`
|
|
42
|
-
const message = (0, __1._stringifyAny)(o, {
|
|
43
|
-
includeErrorData: true,
|
|
44
|
-
...opt,
|
|
45
|
-
});
|
|
46
|
-
return {
|
|
47
|
-
name: 'Error',
|
|
48
|
-
message,
|
|
49
|
-
data: {}, // empty
|
|
50
|
-
};
|
|
57
|
+
Object.assign(eo.data, errorData);
|
|
58
|
+
return eo;
|
|
51
59
|
}
|
|
52
60
|
exports._anyToErrorObject = _anyToErrorObject;
|
|
53
61
|
function _errorToErrorObject(e, includeErrorStack = true) {
|
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
import { CommonLogger } from '../log/commonLogger';
|
|
3
3
|
import type { Promisable } from '../typeFest';
|
|
4
|
+
import { Reviver } from '../types';
|
|
4
5
|
import type { HttpMethod, HttpStatusFamily } from './http.model';
|
|
5
6
|
export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
|
|
6
7
|
logger: CommonLogger;
|
|
@@ -61,6 +62,7 @@ export interface FetcherRetryOptions {
|
|
|
61
62
|
export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
|
|
62
63
|
url: string;
|
|
63
64
|
init: RequestInitNormalized;
|
|
65
|
+
mode: FetcherMode;
|
|
64
66
|
throwHttpErrors: boolean;
|
|
65
67
|
timeoutSeconds: number;
|
|
66
68
|
retry: FetcherRetryOptions;
|
|
@@ -108,6 +110,7 @@ export interface FetcherOptions {
|
|
|
108
110
|
* Defaults to true.
|
|
109
111
|
*/
|
|
110
112
|
retry5xx?: boolean;
|
|
113
|
+
jsonReviver?: Reviver;
|
|
111
114
|
}
|
|
112
115
|
export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
|
|
113
116
|
method: HttpMethod;
|
|
@@ -129,7 +132,7 @@ export interface FetcherResponse<BODY = unknown> {
|
|
|
129
132
|
body?: BODY;
|
|
130
133
|
retryStatus: FetcherRetryStatus;
|
|
131
134
|
}
|
|
132
|
-
export type FetcherMode = 'json' | 'text';
|
|
135
|
+
export type FetcherMode = 'json' | 'text' | 'void';
|
|
133
136
|
/**
|
|
134
137
|
* Experimental wrapper around Fetch.
|
|
135
138
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
|
@@ -146,22 +149,22 @@ export declare class Fetcher {
|
|
|
146
149
|
onBeforeRetry(hook: FetcherBeforeRetryHook): this;
|
|
147
150
|
cfg: FetcherNormalizedCfg;
|
|
148
151
|
static create(cfg?: FetcherCfg & FetcherOptions): Fetcher;
|
|
149
|
-
get: (url: string, opt?: FetcherOptions) => Promise<
|
|
150
|
-
post: (url: string, opt?: FetcherOptions) => Promise<
|
|
151
|
-
put: (url: string, opt?: FetcherOptions) => Promise<
|
|
152
|
-
patch: (url: string, opt?: FetcherOptions) => Promise<
|
|
153
|
-
delete: (url: string, opt?: FetcherOptions) => Promise<
|
|
154
|
-
head: (url: string, opt?: FetcherOptions) => Promise<void>;
|
|
152
|
+
get: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>;
|
|
153
|
+
post: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>;
|
|
154
|
+
put: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>;
|
|
155
|
+
patch: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>;
|
|
156
|
+
delete: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>;
|
|
155
157
|
getText: (url: string, opt?: FetcherOptions) => Promise<string>;
|
|
156
158
|
postText: (url: string, opt?: FetcherOptions) => Promise<string>;
|
|
157
159
|
putText: (url: string, opt?: FetcherOptions) => Promise<string>;
|
|
158
160
|
patchText: (url: string, opt?: FetcherOptions) => Promise<string>;
|
|
159
161
|
deleteText: (url: string, opt?: FetcherOptions) => Promise<string>;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
getVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
|
|
163
|
+
postVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
|
|
164
|
+
putVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
|
|
165
|
+
patchVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
|
|
166
|
+
deleteVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
|
|
167
|
+
headVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
|
|
165
168
|
fetch<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
|
|
166
169
|
rawFetch<T = unknown>(url: string, rawOpt?: FetcherOptions): Promise<FetcherResponse<T>>;
|
|
167
170
|
private processRetry;
|
package/dist/http/fetcher.js
CHANGED
|
@@ -31,12 +31,16 @@ class Fetcher {
|
|
|
31
31
|
// Dynamically create all helper methods
|
|
32
32
|
http_model_1.HTTP_METHODS.forEach(method => {
|
|
33
33
|
// mode=void
|
|
34
|
-
this[method] = async (url, opt) => {
|
|
34
|
+
this[`${method}Void`] = async (url, opt) => {
|
|
35
35
|
return await this.fetch(url, {
|
|
36
36
|
...opt,
|
|
37
37
|
method,
|
|
38
|
+
mode: 'void',
|
|
38
39
|
});
|
|
39
40
|
};
|
|
41
|
+
if (method === 'head')
|
|
42
|
+
return // mode=text
|
|
43
|
+
;
|
|
40
44
|
this[`${method}Text`] = async (url, opt) => {
|
|
41
45
|
return await this.fetch(url, {
|
|
42
46
|
...opt,
|
|
@@ -44,7 +48,8 @@ class Fetcher {
|
|
|
44
48
|
mode: 'text',
|
|
45
49
|
});
|
|
46
50
|
};
|
|
47
|
-
|
|
51
|
+
// mode=json
|
|
52
|
+
this[method] = async (url, opt) => {
|
|
48
53
|
return await this.fetch(url, {
|
|
49
54
|
...opt,
|
|
50
55
|
method,
|
|
@@ -74,7 +79,6 @@ class Fetcher {
|
|
|
74
79
|
static create(cfg = {}) {
|
|
75
80
|
return new Fetcher(cfg);
|
|
76
81
|
}
|
|
77
|
-
// headJson!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
|
|
78
82
|
async fetch(url, opt) {
|
|
79
83
|
const res = await this.rawFetch(url, opt);
|
|
80
84
|
if (res.err) {
|
|
@@ -115,7 +119,7 @@ class Fetcher {
|
|
|
115
119
|
const started = Date.now();
|
|
116
120
|
if (this.cfg.logRequest) {
|
|
117
121
|
const { retryAttempt } = res.retryStatus;
|
|
118
|
-
logger.log([' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`]
|
|
122
|
+
logger.log([' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`]
|
|
119
123
|
.filter(Boolean)
|
|
120
124
|
.join(' '));
|
|
121
125
|
if (this.cfg.logRequestBody && req.init.body) {
|
|
@@ -132,22 +136,38 @@ class Fetcher {
|
|
|
132
136
|
res.statusFamily = this.getStatusFamily(res);
|
|
133
137
|
if (res.fetchResponse?.ok) {
|
|
134
138
|
if (mode === 'json') {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
if (res.fetchResponse.body) {
|
|
140
|
+
const text = await res.fetchResponse.text();
|
|
141
|
+
res.body = text;
|
|
142
|
+
try {
|
|
143
|
+
res.body = JSON.parse(text, req.jsonReviver);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
res.err = (0, error_util_1._anyToError)(err, http_error_1.HttpError, (0, object_util_1._filterNullishValues)({
|
|
147
|
+
httpStatusCode: 0,
|
|
148
|
+
url: req.url,
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// if no body: set responseBody as {}
|
|
154
|
+
// do not throw a "cannot parse null as Json" error
|
|
155
|
+
res.body = {};
|
|
156
|
+
}
|
|
138
157
|
}
|
|
139
158
|
else if (mode === 'text') {
|
|
140
159
|
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
141
160
|
}
|
|
142
161
|
clearTimeout(timeout);
|
|
143
162
|
res.retryStatus.retryStopped = true;
|
|
144
|
-
|
|
163
|
+
// res.err can happen on JSON.parse error
|
|
164
|
+
if (!res.err && this.cfg.logResponse) {
|
|
145
165
|
const { retryAttempt } = res.retryStatus;
|
|
146
166
|
logger.log([
|
|
147
167
|
' <<',
|
|
148
168
|
res.fetchResponse.status,
|
|
149
169
|
signature,
|
|
150
|
-
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
|
|
170
|
+
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
151
171
|
(0, time_util_1._since)(started),
|
|
152
172
|
]
|
|
153
173
|
.filter(Boolean)
|
|
@@ -158,6 +178,7 @@ class Fetcher {
|
|
|
158
178
|
}
|
|
159
179
|
}
|
|
160
180
|
else {
|
|
181
|
+
// !res.ok
|
|
161
182
|
clearTimeout(timeout);
|
|
162
183
|
let errObj;
|
|
163
184
|
if (res.fetchResponse) {
|
|
@@ -268,6 +289,7 @@ class Fetcher {
|
|
|
268
289
|
const norm = (0, object_util_1._merge)({
|
|
269
290
|
baseUrl: '',
|
|
270
291
|
url: '',
|
|
292
|
+
mode: 'void',
|
|
271
293
|
searchParams: {},
|
|
272
294
|
timeoutSeconds: 30,
|
|
273
295
|
throwHttpErrors: true,
|
|
@@ -292,8 +314,9 @@ class Fetcher {
|
|
|
292
314
|
return norm;
|
|
293
315
|
}
|
|
294
316
|
normalizeOptions(url, opt) {
|
|
295
|
-
const { baseUrl, timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry } = this.cfg;
|
|
317
|
+
const { baseUrl, timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry, mode } = this.cfg;
|
|
296
318
|
const req = {
|
|
319
|
+
mode,
|
|
297
320
|
url,
|
|
298
321
|
timeoutSeconds,
|
|
299
322
|
throwHttpErrors,
|
|
@@ -7,11 +7,12 @@ import { AppError, _jsonParseIfPossible, _stringifyAny } from '..';
|
|
|
7
7
|
*
|
|
8
8
|
* Alternatively, if you're sure it's Error - you can use `_assertIsError(err)`.
|
|
9
9
|
*/
|
|
10
|
-
export function _anyToError(o, errorClass = Error, opt) {
|
|
10
|
+
export function _anyToError(o, errorClass = Error, errorData, opt) {
|
|
11
11
|
if (o instanceof errorClass)
|
|
12
12
|
return o;
|
|
13
13
|
// If it's an instance of Error, but ErrorClass is something else (e.g AppError) - it'll be "repacked" into AppError
|
|
14
|
-
const errorObject = _isErrorObject(o) ? o : _anyToErrorObject(o, opt);
|
|
14
|
+
const errorObject = _isErrorObject(o) ? o : _anyToErrorObject(o, {}, opt);
|
|
15
|
+
Object.assign(errorObject.data, errorData);
|
|
15
16
|
return _errorObjectToError(errorObject, errorClass);
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
@@ -20,28 +21,35 @@ export function _anyToError(o, errorClass = Error, opt) {
|
|
|
20
21
|
* If object is Error - Error.message will be used.
|
|
21
22
|
* Objects (not Errors) get converted to prettified JSON string (via `_stringifyAny`).
|
|
22
23
|
*/
|
|
23
|
-
export function _anyToErrorObject(o, opt) {
|
|
24
|
+
export function _anyToErrorObject(o, errorData, opt) {
|
|
24
25
|
var _a;
|
|
26
|
+
let eo;
|
|
25
27
|
if (o instanceof Error) {
|
|
26
|
-
|
|
28
|
+
eo = _errorToErrorObject(o, (_a = opt === null || opt === void 0 ? void 0 : opt.includeErrorStack) !== null && _a !== void 0 ? _a : true);
|
|
27
29
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
else {
|
|
31
|
+
o = _jsonParseIfPossible(o);
|
|
32
|
+
if (_isHttpErrorResponse(o)) {
|
|
33
|
+
eo = o.error;
|
|
34
|
+
}
|
|
35
|
+
else if (_isErrorObject(o)) {
|
|
36
|
+
eo = o;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Here we are sure it has no `data` property,
|
|
40
|
+
// so, fair to return `data: {}` in the end
|
|
41
|
+
// Also we're sure it includes no "error name", e.g no `Error: ...`,
|
|
42
|
+
// so, fair to include `name: 'Error'`
|
|
43
|
+
const message = _stringifyAny(o, Object.assign({ includeErrorData: true }, opt));
|
|
44
|
+
eo = {
|
|
45
|
+
name: 'Error',
|
|
46
|
+
message,
|
|
47
|
+
data: {}, // empty
|
|
48
|
+
};
|
|
49
|
+
}
|
|
31
50
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
// Here we are sure it has no `data` property,
|
|
36
|
-
// so, fair to return `data: {}` in the end
|
|
37
|
-
// Also we're sure it includes no "error name", e.g no `Error: ...`,
|
|
38
|
-
// so, fair to include `name: 'Error'`
|
|
39
|
-
const message = _stringifyAny(o, Object.assign({ includeErrorData: true }, opt));
|
|
40
|
-
return {
|
|
41
|
-
name: 'Error',
|
|
42
|
-
message,
|
|
43
|
-
data: {}, // empty
|
|
44
|
-
};
|
|
51
|
+
Object.assign(eo.data, errorData);
|
|
52
|
+
return eo;
|
|
45
53
|
}
|
|
46
54
|
export function _errorToErrorObject(e, includeErrorStack = true) {
|
|
47
55
|
const obj = {
|
package/dist-esm/http/fetcher.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference lib="dom"/>
|
|
2
2
|
import { __asyncValues } from "tslib";
|
|
3
|
-
import { _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
|
|
3
|
+
import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
|
|
4
4
|
import { HttpError } from '../error/http.error';
|
|
5
5
|
import { _clamp } from '../number/number.util';
|
|
6
6
|
import { _filterNullishValues, _filterUndefinedValues, _mapKeys, _merge, _omit, } from '../object/object.util';
|
|
@@ -29,13 +29,17 @@ export class Fetcher {
|
|
|
29
29
|
// Dynamically create all helper methods
|
|
30
30
|
HTTP_METHODS.forEach(method => {
|
|
31
31
|
// mode=void
|
|
32
|
-
this[method] = async (url, opt) => {
|
|
33
|
-
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method }));
|
|
32
|
+
this[`${method}Void`] = async (url, opt) => {
|
|
33
|
+
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method, mode: 'void' }));
|
|
34
34
|
};
|
|
35
|
+
if (method === 'head')
|
|
36
|
+
return // mode=text
|
|
37
|
+
;
|
|
35
38
|
this[`${method}Text`] = async (url, opt) => {
|
|
36
39
|
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method, mode: 'text' }));
|
|
37
40
|
};
|
|
38
|
-
|
|
41
|
+
// mode=json
|
|
42
|
+
this[method] = async (url, opt) => {
|
|
39
43
|
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method, mode: 'json' }));
|
|
40
44
|
};
|
|
41
45
|
});
|
|
@@ -64,7 +68,6 @@ export class Fetcher {
|
|
|
64
68
|
static create(cfg = {}) {
|
|
65
69
|
return new Fetcher(cfg);
|
|
66
70
|
}
|
|
67
|
-
// headJson!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
|
|
68
71
|
async fetch(url, opt) {
|
|
69
72
|
const res = await this.rawFetch(url, opt);
|
|
70
73
|
if (res.err) {
|
|
@@ -124,7 +127,7 @@ export class Fetcher {
|
|
|
124
127
|
const started = Date.now();
|
|
125
128
|
if (this.cfg.logRequest) {
|
|
126
129
|
const { retryAttempt } = res.retryStatus;
|
|
127
|
-
logger.log([' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`]
|
|
130
|
+
logger.log([' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`]
|
|
128
131
|
.filter(Boolean)
|
|
129
132
|
.join(' '));
|
|
130
133
|
if (this.cfg.logRequestBody && req.init.body) {
|
|
@@ -141,22 +144,38 @@ export class Fetcher {
|
|
|
141
144
|
res.statusFamily = this.getStatusFamily(res);
|
|
142
145
|
if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
|
|
143
146
|
if (mode === 'json') {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
if (res.fetchResponse.body) {
|
|
148
|
+
const text = await res.fetchResponse.text();
|
|
149
|
+
res.body = text;
|
|
150
|
+
try {
|
|
151
|
+
res.body = JSON.parse(text, req.jsonReviver);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
res.err = _anyToError(err, HttpError, _filterNullishValues({
|
|
155
|
+
httpStatusCode: 0,
|
|
156
|
+
url: req.url,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// if no body: set responseBody as {}
|
|
162
|
+
// do not throw a "cannot parse null as Json" error
|
|
163
|
+
res.body = {};
|
|
164
|
+
}
|
|
147
165
|
}
|
|
148
166
|
else if (mode === 'text') {
|
|
149
167
|
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
150
168
|
}
|
|
151
169
|
clearTimeout(timeout);
|
|
152
170
|
res.retryStatus.retryStopped = true;
|
|
153
|
-
|
|
171
|
+
// res.err can happen on JSON.parse error
|
|
172
|
+
if (!res.err && this.cfg.logResponse) {
|
|
154
173
|
const { retryAttempt } = res.retryStatus;
|
|
155
174
|
logger.log([
|
|
156
175
|
' <<',
|
|
157
176
|
res.fetchResponse.status,
|
|
158
177
|
signature,
|
|
159
|
-
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
|
|
178
|
+
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
160
179
|
_since(started),
|
|
161
180
|
]
|
|
162
181
|
.filter(Boolean)
|
|
@@ -167,6 +186,7 @@ export class Fetcher {
|
|
|
167
186
|
}
|
|
168
187
|
}
|
|
169
188
|
else {
|
|
189
|
+
// !res.ok
|
|
170
190
|
clearTimeout(timeout);
|
|
171
191
|
let errObj;
|
|
172
192
|
if (res.fetchResponse) {
|
|
@@ -310,6 +330,7 @@ export class Fetcher {
|
|
|
310
330
|
const norm = _merge({
|
|
311
331
|
baseUrl: '',
|
|
312
332
|
url: '',
|
|
333
|
+
mode: 'void',
|
|
313
334
|
searchParams: {},
|
|
314
335
|
timeoutSeconds: 30,
|
|
315
336
|
throwHttpErrors: true,
|
|
@@ -334,8 +355,9 @@ export class Fetcher {
|
|
|
334
355
|
return norm;
|
|
335
356
|
}
|
|
336
357
|
normalizeOptions(url, opt) {
|
|
337
|
-
const { baseUrl, timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry } = this.cfg;
|
|
338
|
-
const req = Object.assign(Object.assign({
|
|
358
|
+
const { baseUrl, timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry, mode } = this.cfg;
|
|
359
|
+
const req = Object.assign(Object.assign({ mode,
|
|
360
|
+
url,
|
|
339
361
|
timeoutSeconds,
|
|
340
362
|
throwHttpErrors,
|
|
341
363
|
retryPost,
|
package/package.json
CHANGED
package/src/error/error.util.ts
CHANGED
|
@@ -19,13 +19,15 @@ import { AppError, _jsonParseIfPossible, _stringifyAny } from '..'
|
|
|
19
19
|
export function _anyToError<ERROR_TYPE extends Error = Error>(
|
|
20
20
|
o: any,
|
|
21
21
|
errorClass: Class<ERROR_TYPE> = Error as any,
|
|
22
|
+
errorData?: ErrorData,
|
|
22
23
|
opt?: StringifyAnyOptions,
|
|
23
24
|
): ERROR_TYPE {
|
|
24
25
|
if (o instanceof errorClass) return o
|
|
25
26
|
|
|
26
27
|
// If it's an instance of Error, but ErrorClass is something else (e.g AppError) - it'll be "repacked" into AppError
|
|
27
28
|
|
|
28
|
-
const errorObject = _isErrorObject(o) ? o : _anyToErrorObject(o, opt)
|
|
29
|
+
const errorObject = _isErrorObject(o) ? o : _anyToErrorObject(o, {}, opt)
|
|
30
|
+
Object.assign(errorObject.data, errorData)
|
|
29
31
|
return _errorObjectToError(errorObject, errorClass)
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -37,37 +39,40 @@ export function _anyToError<ERROR_TYPE extends Error = Error>(
|
|
|
37
39
|
*/
|
|
38
40
|
export function _anyToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
|
|
39
41
|
o: any,
|
|
42
|
+
errorData?: Partial<DATA_TYPE>,
|
|
40
43
|
opt?: StringifyAnyOptions,
|
|
41
44
|
): ErrorObject<DATA_TYPE> {
|
|
42
|
-
|
|
43
|
-
return _errorToErrorObject<DATA_TYPE>(o, opt?.includeErrorStack ?? true)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
o = _jsonParseIfPossible(o)
|
|
47
|
-
|
|
48
|
-
if (_isHttpErrorResponse(o)) {
|
|
49
|
-
return o.error as any
|
|
50
|
-
}
|
|
45
|
+
let eo: ErrorObject<DATA_TYPE>
|
|
51
46
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
47
|
+
if (o instanceof Error) {
|
|
48
|
+
eo = _errorToErrorObject<DATA_TYPE>(o, opt?.includeErrorStack ?? true)
|
|
49
|
+
} else {
|
|
50
|
+
o = _jsonParseIfPossible(o)
|
|
51
|
+
|
|
52
|
+
if (_isHttpErrorResponse(o)) {
|
|
53
|
+
eo = o.error as any
|
|
54
|
+
} else if (_isErrorObject(o)) {
|
|
55
|
+
eo = o as ErrorObject<DATA_TYPE>
|
|
56
|
+
} else {
|
|
57
|
+
// Here we are sure it has no `data` property,
|
|
58
|
+
// so, fair to return `data: {}` in the end
|
|
59
|
+
// Also we're sure it includes no "error name", e.g no `Error: ...`,
|
|
60
|
+
// so, fair to include `name: 'Error'`
|
|
61
|
+
const message = _stringifyAny(o, {
|
|
62
|
+
includeErrorData: true, // cause we're returning an ErrorObject, not a stringified error (yet)
|
|
63
|
+
...opt,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
eo = {
|
|
67
|
+
name: 'Error',
|
|
68
|
+
message,
|
|
69
|
+
data: {} as DATA_TYPE, // empty
|
|
70
|
+
}
|
|
71
|
+
}
|
|
54
72
|
}
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Also we're sure it includes no "error name", e.g no `Error: ...`,
|
|
59
|
-
// so, fair to include `name: 'Error'`
|
|
60
|
-
|
|
61
|
-
const message = _stringifyAny(o, {
|
|
62
|
-
includeErrorData: true, // cause we're returning an ErrorObject, not a stringified error (yet)
|
|
63
|
-
...opt,
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
name: 'Error',
|
|
68
|
-
message,
|
|
69
|
-
data: {} as DATA_TYPE, // empty
|
|
70
|
-
}
|
|
74
|
+
Object.assign(eo.data, errorData)
|
|
75
|
+
return eo
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
export function _errorToErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
|
package/src/http/fetcher.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="dom"/>
|
|
2
2
|
|
|
3
3
|
import { ErrorObject } from '../error/error.model'
|
|
4
|
-
import { _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
|
|
4
|
+
import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
|
|
5
5
|
import { HttpError } from '../error/http.error'
|
|
6
6
|
import { CommonLogger } from '../log/commonLogger'
|
|
7
7
|
import { _clamp } from '../number/number.util'
|
|
@@ -16,6 +16,7 @@ import { pDelay } from '../promise/pDelay'
|
|
|
16
16
|
import { _jsonParseIfPossible } from '../string/json.util'
|
|
17
17
|
import { _since } from '../time/time.util'
|
|
18
18
|
import type { Promisable } from '../typeFest'
|
|
19
|
+
import { Reviver } from '../types'
|
|
19
20
|
import { HTTP_METHODS } from './http.model'
|
|
20
21
|
import type { HttpMethod, HttpStatusFamily } from './http.model'
|
|
21
22
|
|
|
@@ -86,6 +87,7 @@ export interface FetcherRetryOptions {
|
|
|
86
87
|
export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
|
|
87
88
|
url: string
|
|
88
89
|
init: RequestInitNormalized
|
|
90
|
+
mode: FetcherMode
|
|
89
91
|
throwHttpErrors: boolean
|
|
90
92
|
timeoutSeconds: number
|
|
91
93
|
retry: FetcherRetryOptions
|
|
@@ -121,7 +123,7 @@ export interface FetcherOptions {
|
|
|
121
123
|
// init?: Partial<RequestInitNormalized>
|
|
122
124
|
|
|
123
125
|
headers?: Record<string, any>
|
|
124
|
-
mode?: FetcherMode // default to
|
|
126
|
+
mode?: FetcherMode // default to 'void'
|
|
125
127
|
|
|
126
128
|
searchParams?: Record<string, any>
|
|
127
129
|
|
|
@@ -144,6 +146,8 @@ export interface FetcherOptions {
|
|
|
144
146
|
* Defaults to true.
|
|
145
147
|
*/
|
|
146
148
|
retry5xx?: boolean
|
|
149
|
+
|
|
150
|
+
jsonReviver?: Reviver
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
|
|
@@ -170,7 +174,7 @@ export interface FetcherResponse<BODY = unknown> {
|
|
|
170
174
|
retryStatus: FetcherRetryStatus
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
export type FetcherMode = 'json' | 'text'
|
|
177
|
+
export type FetcherMode = 'json' | 'text' | 'void'
|
|
174
178
|
|
|
175
179
|
const defRetryOptions: FetcherRetryOptions = {
|
|
176
180
|
count: 2,
|
|
@@ -196,14 +200,15 @@ export class Fetcher {
|
|
|
196
200
|
// Dynamically create all helper methods
|
|
197
201
|
HTTP_METHODS.forEach(method => {
|
|
198
202
|
// mode=void
|
|
199
|
-
this[method] = async (url: string, opt?: FetcherOptions): Promise<void> => {
|
|
203
|
+
this[`${method}Void`] = async (url: string, opt?: FetcherOptions): Promise<void> => {
|
|
200
204
|
return await this.fetch<void>(url, {
|
|
201
205
|
...opt,
|
|
202
206
|
method,
|
|
207
|
+
mode: 'void',
|
|
203
208
|
})
|
|
204
209
|
}
|
|
205
210
|
|
|
206
|
-
// mode=text
|
|
211
|
+
if (method === 'head') return // mode=text
|
|
207
212
|
;(this as any)[`${method}Text`] = async (
|
|
208
213
|
url: string,
|
|
209
214
|
opt?: FetcherOptions,
|
|
@@ -216,10 +221,7 @@ export class Fetcher {
|
|
|
216
221
|
}
|
|
217
222
|
|
|
218
223
|
// mode=json
|
|
219
|
-
|
|
220
|
-
url: string,
|
|
221
|
-
opt?: FetcherOptions,
|
|
222
|
-
): Promise<T> => {
|
|
224
|
+
this[method] = async <T = unknown>(url: string, opt?: FetcherOptions): Promise<T> => {
|
|
223
225
|
return await this.fetch<T>(url, {
|
|
224
226
|
...opt,
|
|
225
227
|
method,
|
|
@@ -254,25 +256,27 @@ export class Fetcher {
|
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
// These methods are generated dynamically in the constructor
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
// These default methods use mode=json
|
|
260
|
+
get!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
|
|
261
|
+
post!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
|
|
262
|
+
put!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
|
|
263
|
+
patch!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
|
|
264
|
+
delete!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
|
|
265
|
+
|
|
266
|
+
// mode=text
|
|
264
267
|
getText!: (url: string, opt?: FetcherOptions) => Promise<string>
|
|
265
268
|
postText!: (url: string, opt?: FetcherOptions) => Promise<string>
|
|
266
269
|
putText!: (url: string, opt?: FetcherOptions) => Promise<string>
|
|
267
270
|
patchText!: (url: string, opt?: FetcherOptions) => Promise<string>
|
|
268
271
|
deleteText!: (url: string, opt?: FetcherOptions) => Promise<string>
|
|
269
272
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
// mode=void (no body fetching/parsing)
|
|
274
|
+
getVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
|
|
275
|
+
postVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
|
|
276
|
+
putVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
|
|
277
|
+
patchVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
|
|
278
|
+
deleteVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
|
|
279
|
+
headVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
|
|
276
280
|
|
|
277
281
|
async fetch<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
|
|
278
282
|
const res = await this.rawFetch<T>(url, opt)
|
|
@@ -329,7 +333,7 @@ export class Fetcher {
|
|
|
329
333
|
if (this.cfg.logRequest) {
|
|
330
334
|
const { retryAttempt } = res.retryStatus
|
|
331
335
|
logger.log(
|
|
332
|
-
[' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`]
|
|
336
|
+
[' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`]
|
|
333
337
|
.filter(Boolean)
|
|
334
338
|
.join(' '),
|
|
335
339
|
)
|
|
@@ -348,9 +352,27 @@ export class Fetcher {
|
|
|
348
352
|
|
|
349
353
|
if (res.fetchResponse?.ok) {
|
|
350
354
|
if (mode === 'json') {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
355
|
+
if (res.fetchResponse.body) {
|
|
356
|
+
const text = await res.fetchResponse.text()
|
|
357
|
+
res.body = text
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
res.body = JSON.parse(text, req.jsonReviver)
|
|
361
|
+
} catch (err) {
|
|
362
|
+
res.err = _anyToError(
|
|
363
|
+
err,
|
|
364
|
+
HttpError,
|
|
365
|
+
_filterNullishValues({
|
|
366
|
+
httpStatusCode: 0,
|
|
367
|
+
url: req.url,
|
|
368
|
+
}),
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
// if no body: set responseBody as {}
|
|
373
|
+
// do not throw a "cannot parse null as Json" error
|
|
374
|
+
res.body = {}
|
|
375
|
+
}
|
|
354
376
|
} else if (mode === 'text') {
|
|
355
377
|
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : ''
|
|
356
378
|
}
|
|
@@ -358,14 +380,15 @@ export class Fetcher {
|
|
|
358
380
|
clearTimeout(timeout)
|
|
359
381
|
res.retryStatus.retryStopped = true
|
|
360
382
|
|
|
361
|
-
|
|
383
|
+
// res.err can happen on JSON.parse error
|
|
384
|
+
if (!res.err && this.cfg.logResponse) {
|
|
362
385
|
const { retryAttempt } = res.retryStatus
|
|
363
386
|
logger.log(
|
|
364
387
|
[
|
|
365
388
|
' <<',
|
|
366
389
|
res.fetchResponse.status,
|
|
367
390
|
signature,
|
|
368
|
-
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
|
|
391
|
+
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
369
392
|
_since(started),
|
|
370
393
|
]
|
|
371
394
|
.filter(Boolean)
|
|
@@ -377,6 +400,7 @@ export class Fetcher {
|
|
|
377
400
|
}
|
|
378
401
|
}
|
|
379
402
|
} else {
|
|
403
|
+
// !res.ok
|
|
380
404
|
clearTimeout(timeout)
|
|
381
405
|
|
|
382
406
|
let errObj: ErrorObject
|
|
@@ -500,6 +524,7 @@ export class Fetcher {
|
|
|
500
524
|
{
|
|
501
525
|
baseUrl: '',
|
|
502
526
|
url: '',
|
|
527
|
+
mode: 'void',
|
|
503
528
|
searchParams: {},
|
|
504
529
|
timeoutSeconds: 30,
|
|
505
530
|
throwHttpErrors: true,
|
|
@@ -529,10 +554,11 @@ export class Fetcher {
|
|
|
529
554
|
}
|
|
530
555
|
|
|
531
556
|
private normalizeOptions(url: string, opt: FetcherOptions): FetcherRequest {
|
|
532
|
-
const { baseUrl, timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry } =
|
|
557
|
+
const { baseUrl, timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry, mode } =
|
|
533
558
|
this.cfg
|
|
534
559
|
|
|
535
560
|
const req: FetcherRequest = {
|
|
561
|
+
mode,
|
|
536
562
|
url,
|
|
537
563
|
timeoutSeconds,
|
|
538
564
|
throwHttpErrors,
|