@naturalcycles/js-lib 14.157.1 → 14.158.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 +6 -0
- package/dist/error/assert.js +13 -1
- package/dist/error/httpRequestError.d.ts +7 -1
- package/dist/error/try.d.ts +7 -9
- package/dist/error/try.js +22 -12
- package/dist/http/fetcher.d.ts +23 -2
- package/dist/http/fetcher.js +42 -2
- package/dist/http/fetcher.model.d.ts +6 -0
- package/dist/types.d.ts +11 -0
- package/dist-esm/error/assert.js +11 -0
- package/dist-esm/error/try.js +20 -11
- package/dist-esm/http/fetcher.js +40 -1
- package/package.json +1 -1
- package/src/error/assert.ts +15 -0
- package/src/error/httpRequestError.ts +8 -1
- package/src/error/try.ts +32 -16
- package/src/http/fetcher.model.ts +8 -1
- package/src/http/fetcher.ts +50 -4
- package/src/types.ts +12 -0
package/dist/error/assert.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export declare function _assertEquals<T>(actual: any, expected: T, message?: str
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function _assertDeepEquals<T>(actual: any, expected: T, message?: string, errorData?: ErrorData): asserts actual is T;
|
|
35
35
|
export declare function _assertIsError<ERR extends Error = Error>(err: any, errorClass?: Class<ERR>): asserts err is ERR;
|
|
36
|
+
/**
|
|
37
|
+
* Asserts that passed object is indeed an Error of defined ErrorClass.
|
|
38
|
+
* If yes - returns peacefully (with TypeScript assertion).
|
|
39
|
+
* In not - throws (re-throws) that error up.
|
|
40
|
+
*/
|
|
41
|
+
export declare function _assertErrorClassOrRethrow<ERR extends Error>(err: any, errorClass: Class<ERR>): asserts err is ERR;
|
|
36
42
|
export declare function _assertIsErrorObject<DATA_TYPE extends ErrorData = ErrorData>(obj: any): asserts obj is ErrorObject<DATA_TYPE>;
|
|
37
43
|
export declare function _assertIsString(v: any, message?: string): asserts v is string;
|
|
38
44
|
export declare function _assertIsNumber(v: any, message?: string): asserts v is number;
|
package/dist/error/assert.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AssertionError = exports._assertTypeOf = exports._assertIsNumber = exports._assertIsString = exports._assertIsErrorObject = exports._assertIsError = exports._assertDeepEquals = exports._assertEquals = exports._assert = void 0;
|
|
3
|
+
exports.AssertionError = exports._assertTypeOf = exports._assertIsNumber = exports._assertIsString = exports._assertIsErrorObject = exports._assertErrorClassOrRethrow = exports._assertIsError = exports._assertDeepEquals = exports._assertEquals = exports._assert = void 0;
|
|
4
4
|
const __1 = require("..");
|
|
5
5
|
const app_error_1 = require("./app.error");
|
|
6
6
|
/**
|
|
@@ -84,6 +84,18 @@ function _assertIsError(err, errorClass = Error) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
exports._assertIsError = _assertIsError;
|
|
87
|
+
/**
|
|
88
|
+
* Asserts that passed object is indeed an Error of defined ErrorClass.
|
|
89
|
+
* If yes - returns peacefully (with TypeScript assertion).
|
|
90
|
+
* In not - throws (re-throws) that error up.
|
|
91
|
+
*/
|
|
92
|
+
function _assertErrorClassOrRethrow(err, errorClass) {
|
|
93
|
+
if (!(err instanceof errorClass)) {
|
|
94
|
+
// re-throw
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports._assertErrorClassOrRethrow = _assertErrorClassOrRethrow;
|
|
87
99
|
function _assertIsErrorObject(obj) {
|
|
88
100
|
if (!(0, __1._isErrorObject)(obj)) {
|
|
89
101
|
const msg = [`expected to be ErrorObject`, `actual typeof: ${typeof obj}`].join('\n');
|
|
@@ -18,5 +18,11 @@ import type { ErrorObject, HttpRequestErrorData } from './error.model';
|
|
|
18
18
|
* (by default).
|
|
19
19
|
*/
|
|
20
20
|
export declare class HttpRequestError extends AppError<HttpRequestErrorData> {
|
|
21
|
-
constructor(message: string, data: HttpRequestErrorData, cause
|
|
21
|
+
constructor(message: string, data: HttpRequestErrorData, cause: ErrorObject);
|
|
22
|
+
/**
|
|
23
|
+
* Cause is strictly-defined for HttpRequestError,
|
|
24
|
+
* so it always has a cause.
|
|
25
|
+
* (for dev convenience)
|
|
26
|
+
*/
|
|
27
|
+
cause: ErrorObject;
|
|
22
28
|
}
|
package/dist/error/try.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Class } from '../typeFest';
|
|
2
|
-
import type { AnyFunction } from '../types';
|
|
2
|
+
import type { AnyFunction, ErrorDataTuple } from '../types';
|
|
3
3
|
import { AppError } from './app.error';
|
|
4
4
|
/**
|
|
5
5
|
* Calls a function, returns a Tuple of [error, value].
|
|
@@ -8,9 +8,6 @@ import { AppError } from './app.error';
|
|
|
8
8
|
*
|
|
9
9
|
* Similar to pTry, but for sync functions.
|
|
10
10
|
*
|
|
11
|
-
* For convenience, second argument type is non-optional,
|
|
12
|
-
* so you can use it without `!`. But you SHOULD always check `if (err)` first!
|
|
13
|
-
*
|
|
14
11
|
* ERR is typed as Error, not `unknown`. While unknown would be more correct,
|
|
15
12
|
* according to recent TypeScript, Error gives more developer convenience.
|
|
16
13
|
* In our code we NEVER throw non-errors.
|
|
@@ -23,14 +20,11 @@ import { AppError } from './app.error';
|
|
|
23
20
|
* if (err) ...do something...
|
|
24
21
|
* v // go ahead and use v
|
|
25
22
|
*/
|
|
26
|
-
export declare function _try<ERR
|
|
23
|
+
export declare function _try<T, ERR extends Error = Error>(fn: () => T, errorClass?: Class<ERR>): ErrorDataTuple<T, ERR>;
|
|
27
24
|
/**
|
|
28
25
|
* Like _try, but for Promises.
|
|
29
|
-
*
|
|
30
|
-
* Also, intentionally types second return item as non-optional,
|
|
31
|
-
* but you should check for `err` presense first!
|
|
32
26
|
*/
|
|
33
|
-
export declare function pTry<ERR
|
|
27
|
+
export declare function pTry<T, ERR extends Error = Error>(promise: Promise<T>, errorClass?: Class<ERR>): Promise<ErrorDataTuple<Awaited<T>, ERR>>;
|
|
34
28
|
/**
|
|
35
29
|
* It is thrown when Error was expected, but didn't happen
|
|
36
30
|
* ("pass" happened instead).
|
|
@@ -61,3 +55,7 @@ export declare function pExpectedError<ERR = Error>(promise: Promise<any>, error
|
|
|
61
55
|
* Shortcut function to simplify error snapshot-matching in tests.
|
|
62
56
|
*/
|
|
63
57
|
export declare function pExpectedErrorString<ERR = Error>(promise: Promise<any>, errorClass?: Class<ERR>): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Shortcut function to simplify error snapshot-matching in tests.
|
|
60
|
+
*/
|
|
61
|
+
export declare function _expectedErrorString<ERR = Error>(fn: AnyFunction, errorClass?: Class<ERR>): string;
|
package/dist/error/try.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pExpectedErrorString = exports.pExpectedError = exports._expectedError = exports.UnexpectedPassError = exports.pTry = exports._try = void 0;
|
|
3
|
+
exports._expectedErrorString = exports.pExpectedErrorString = exports.pExpectedError = exports._expectedError = exports.UnexpectedPassError = exports.pTry = exports._try = void 0;
|
|
4
4
|
const stringifyAny_1 = require("../string/stringifyAny");
|
|
5
5
|
const app_error_1 = require("./app.error");
|
|
6
|
+
const assert_1 = require("./assert");
|
|
6
7
|
/**
|
|
7
8
|
* Calls a function, returns a Tuple of [error, value].
|
|
8
9
|
* Allows to write shorter code that avoids `try/catch`.
|
|
@@ -10,9 +11,6 @@ const app_error_1 = require("./app.error");
|
|
|
10
11
|
*
|
|
11
12
|
* Similar to pTry, but for sync functions.
|
|
12
13
|
*
|
|
13
|
-
* For convenience, second argument type is non-optional,
|
|
14
|
-
* so you can use it without `!`. But you SHOULD always check `if (err)` first!
|
|
15
|
-
*
|
|
16
14
|
* ERR is typed as Error, not `unknown`. While unknown would be more correct,
|
|
17
15
|
* according to recent TypeScript, Error gives more developer convenience.
|
|
18
16
|
* In our code we NEVER throw non-errors.
|
|
@@ -25,27 +23,30 @@ const app_error_1 = require("./app.error");
|
|
|
25
23
|
* if (err) ...do something...
|
|
26
24
|
* v // go ahead and use v
|
|
27
25
|
*/
|
|
28
|
-
function _try(fn) {
|
|
26
|
+
function _try(fn, errorClass) {
|
|
29
27
|
try {
|
|
30
28
|
return [null, fn()];
|
|
31
29
|
}
|
|
32
30
|
catch (err) {
|
|
33
|
-
|
|
31
|
+
if (errorClass) {
|
|
32
|
+
(0, assert_1._assertErrorClassOrRethrow)(err, errorClass);
|
|
33
|
+
}
|
|
34
|
+
return [err, null];
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
exports._try = _try;
|
|
37
38
|
/**
|
|
38
39
|
* Like _try, but for Promises.
|
|
39
|
-
*
|
|
40
|
-
* Also, intentionally types second return item as non-optional,
|
|
41
|
-
* but you should check for `err` presense first!
|
|
42
40
|
*/
|
|
43
|
-
async function pTry(promise) {
|
|
41
|
+
async function pTry(promise, errorClass) {
|
|
44
42
|
try {
|
|
45
43
|
return [null, await promise];
|
|
46
44
|
}
|
|
47
45
|
catch (err) {
|
|
48
|
-
|
|
46
|
+
if (errorClass) {
|
|
47
|
+
(0, assert_1._assertErrorClassOrRethrow)(err, errorClass);
|
|
48
|
+
}
|
|
49
|
+
return [err, null];
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
exports.pTry = pTry;
|
|
@@ -114,6 +115,15 @@ exports.pExpectedError = pExpectedError;
|
|
|
114
115
|
* Shortcut function to simplify error snapshot-matching in tests.
|
|
115
116
|
*/
|
|
116
117
|
async function pExpectedErrorString(promise, errorClass) {
|
|
117
|
-
|
|
118
|
+
const err = await pExpectedError(promise, errorClass);
|
|
119
|
+
return (0, stringifyAny_1._stringifyAny)(err);
|
|
118
120
|
}
|
|
119
121
|
exports.pExpectedErrorString = pExpectedErrorString;
|
|
122
|
+
/**
|
|
123
|
+
* Shortcut function to simplify error snapshot-matching in tests.
|
|
124
|
+
*/
|
|
125
|
+
function _expectedErrorString(fn, errorClass) {
|
|
126
|
+
const err = _expectedError(fn, errorClass);
|
|
127
|
+
return (0, stringifyAny_1._stringifyAny)(err);
|
|
128
|
+
}
|
|
129
|
+
exports._expectedErrorString = _expectedErrorString;
|
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/// <reference lib="dom.iterable" />
|
|
3
|
-
import
|
|
3
|
+
import { HttpRequestError } from '../error/httpRequestError';
|
|
4
|
+
import { ErrorDataTuple } from '../types';
|
|
5
|
+
import type { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherNormalizedCfg, FetcherOptions, FetcherResponse, FetcherMockFunction, RequestInitNormalized } from './fetcher.model';
|
|
6
|
+
/**
|
|
7
|
+
* Allows to mock Fetcher.callNativeFetch globally (for all Fetcher instances out there).
|
|
8
|
+
*
|
|
9
|
+
* Call it with `null` to reset/unmock.
|
|
10
|
+
*
|
|
11
|
+
* Please note that jest.spyOn(someFetcher, 'callNativeFetch') has higher priority than
|
|
12
|
+
* a global mock, so it'll run instead of a global mock.
|
|
13
|
+
*/
|
|
14
|
+
export declare function setGlobalFetcherMock(fn: FetcherMockFunction | null): void;
|
|
4
15
|
/**
|
|
5
16
|
* Experimental wrapper around Fetch.
|
|
6
17
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
|
@@ -39,17 +50,27 @@ export declare class Fetcher {
|
|
|
39
50
|
*/
|
|
40
51
|
getReadableStream(url: string, opt?: FetcherOptions): Promise<ReadableStream<Uint8Array>>;
|
|
41
52
|
fetch<T = unknown>(opt: FetcherOptions): Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Like pTry - returns a [err, data] tuple (aka ErrorDataTuple).
|
|
55
|
+
* err, if defined, is strictly HttpRequestError.
|
|
56
|
+
* UPD: actually not, err is typed as Error, as it feels unsafe to guarantee error type.
|
|
57
|
+
* UPD: actually yes - it will return HttpRequestError, and throw if there's an error
|
|
58
|
+
* of any other type.
|
|
59
|
+
*/
|
|
60
|
+
tryFetch<T = unknown>(opt: FetcherOptions): Promise<ErrorDataTuple<T, HttpRequestError>>;
|
|
42
61
|
/**
|
|
43
62
|
* Returns FetcherResponse.
|
|
44
63
|
* Never throws, returns `err` property in the response instead.
|
|
45
64
|
* Use this method instead of `throwHttpErrors: false` or try-catching.
|
|
65
|
+
*
|
|
66
|
+
* Note: responseType defaults to `void`, so, override it if you expect different.
|
|
46
67
|
*/
|
|
47
68
|
doFetch<T = unknown>(opt: FetcherOptions): Promise<FetcherResponse<T>>;
|
|
48
69
|
private onOkResponse;
|
|
49
70
|
/**
|
|
50
71
|
* This method exists to be able to easily mock it.
|
|
51
72
|
*/
|
|
52
|
-
callNativeFetch(url: string, init:
|
|
73
|
+
callNativeFetch(url: string, init: RequestInitNormalized): Promise<Response>;
|
|
53
74
|
private onNotOkResponse;
|
|
54
75
|
private processRetry;
|
|
55
76
|
private getRetryTimeout;
|
package/dist/http/fetcher.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
/// <reference lib="dom"/>
|
|
3
3
|
/// <reference lib="dom.iterable"/>
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.getFetcher = exports.Fetcher = void 0;
|
|
5
|
+
exports.getFetcher = exports.Fetcher = exports.setGlobalFetcherMock = void 0;
|
|
6
6
|
const env_1 = require("../env");
|
|
7
|
+
const assert_1 = require("../error/assert");
|
|
7
8
|
const error_util_1 = require("../error/error.util");
|
|
8
9
|
const httpRequestError_1 = require("../error/httpRequestError");
|
|
9
10
|
const number_util_1 = require("../number/number.util");
|
|
@@ -28,6 +29,19 @@ const defRetryOptions = {
|
|
|
28
29
|
timeoutMax: 30000,
|
|
29
30
|
timeoutMultiplier: 2,
|
|
30
31
|
};
|
|
32
|
+
let globalFetcherMock = null;
|
|
33
|
+
/**
|
|
34
|
+
* Allows to mock Fetcher.callNativeFetch globally (for all Fetcher instances out there).
|
|
35
|
+
*
|
|
36
|
+
* Call it with `null` to reset/unmock.
|
|
37
|
+
*
|
|
38
|
+
* Please note that jest.spyOn(someFetcher, 'callNativeFetch') has higher priority than
|
|
39
|
+
* a global mock, so it'll run instead of a global mock.
|
|
40
|
+
*/
|
|
41
|
+
function setGlobalFetcherMock(fn) {
|
|
42
|
+
globalFetcherMock = fn;
|
|
43
|
+
}
|
|
44
|
+
exports.setGlobalFetcherMock = setGlobalFetcherMock;
|
|
31
45
|
/**
|
|
32
46
|
* Experimental wrapper around Fetch.
|
|
33
47
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
|
@@ -112,10 +126,27 @@ class Fetcher {
|
|
|
112
126
|
}
|
|
113
127
|
return res.body;
|
|
114
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Like pTry - returns a [err, data] tuple (aka ErrorDataTuple).
|
|
131
|
+
* err, if defined, is strictly HttpRequestError.
|
|
132
|
+
* UPD: actually not, err is typed as Error, as it feels unsafe to guarantee error type.
|
|
133
|
+
* UPD: actually yes - it will return HttpRequestError, and throw if there's an error
|
|
134
|
+
* of any other type.
|
|
135
|
+
*/
|
|
136
|
+
async tryFetch(opt) {
|
|
137
|
+
const res = await this.doFetch(opt);
|
|
138
|
+
if (res.err) {
|
|
139
|
+
(0, assert_1._assertErrorClassOrRethrow)(res.err, httpRequestError_1.HttpRequestError);
|
|
140
|
+
return [res.err, null];
|
|
141
|
+
}
|
|
142
|
+
return [null, res.body];
|
|
143
|
+
}
|
|
115
144
|
/**
|
|
116
145
|
* Returns FetcherResponse.
|
|
117
146
|
* Never throws, returns `err` property in the response instead.
|
|
118
147
|
* Use this method instead of `throwHttpErrors: false` or try-catching.
|
|
148
|
+
*
|
|
149
|
+
* Note: responseType defaults to `void`, so, override it if you expect different.
|
|
119
150
|
*/
|
|
120
151
|
async doFetch(opt) {
|
|
121
152
|
const req = this.normalizeOptions(opt);
|
|
@@ -267,6 +298,10 @@ class Fetcher {
|
|
|
267
298
|
* This method exists to be able to easily mock it.
|
|
268
299
|
*/
|
|
269
300
|
async callNativeFetch(url, init) {
|
|
301
|
+
// If global mock is defined - call it instead of the native Fetch
|
|
302
|
+
if (globalFetcherMock) {
|
|
303
|
+
return await globalFetcherMock(url, init);
|
|
304
|
+
}
|
|
270
305
|
return await globalThis.fetch(url, init);
|
|
271
306
|
}
|
|
272
307
|
async onNotOkResponse(res) {
|
|
@@ -283,6 +318,11 @@ class Fetcher {
|
|
|
283
318
|
cause = (0, error_util_1._anyToErrorObject)(body);
|
|
284
319
|
}
|
|
285
320
|
}
|
|
321
|
+
cause ||= {
|
|
322
|
+
name: 'Error',
|
|
323
|
+
message: 'Fetch failed',
|
|
324
|
+
data: {},
|
|
325
|
+
};
|
|
286
326
|
const message = [res.fetchResponse?.status, res.signature].filter(Boolean).join(' ');
|
|
287
327
|
res.err = new httpRequestError_1.HttpRequestError(message, (0, object_util_1._filterNullishValues)({
|
|
288
328
|
responseStatusCode: res.fetchResponse?.status || 0,
|
|
@@ -441,7 +481,7 @@ class Fetcher {
|
|
|
441
481
|
const norm = (0, object_util_1._merge)({
|
|
442
482
|
baseUrl: '',
|
|
443
483
|
inputUrl: '',
|
|
444
|
-
responseType: '
|
|
484
|
+
responseType: 'json',
|
|
445
485
|
searchParams: {},
|
|
446
486
|
timeoutSeconds: 30,
|
|
447
487
|
retryPost: false,
|
|
@@ -205,3 +205,9 @@ export interface FetcherErrorResponse<BODY = unknown> {
|
|
|
205
205
|
}
|
|
206
206
|
export type FetcherResponse<BODY = unknown> = FetcherSuccessResponse<BODY> | FetcherErrorResponse<BODY>;
|
|
207
207
|
export type FetcherResponseType = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob' | 'readableStream';
|
|
208
|
+
/**
|
|
209
|
+
* Function that mocks `Fetcher.callNativeFetch` globally.
|
|
210
|
+
*
|
|
211
|
+
* url is `fullUrl` (includes baseUrl and all).
|
|
212
|
+
*/
|
|
213
|
+
export type FetcherMockFunction = (url: string, init: RequestInitNormalized) => Promise<Response>;
|
package/dist/types.d.ts
CHANGED
|
@@ -207,3 +207,14 @@ export declare function _typeCast<T>(v: any): asserts v is T;
|
|
|
207
207
|
* Type-safe Object.assign that checks that part is indeed a Partial<T>
|
|
208
208
|
*/
|
|
209
209
|
export declare const _objectAssign: <T extends AnyObject>(target: T, part: Partial<T>) => T;
|
|
210
|
+
/**
|
|
211
|
+
* Defines a tuple of [err, data]
|
|
212
|
+
* where only 1 of them exists.
|
|
213
|
+
* Either error exists and data is null
|
|
214
|
+
* Or error is null and data is defined.
|
|
215
|
+
* This forces you to check `if (err)`, which lets
|
|
216
|
+
* TypeScript infer the existence of `data`.
|
|
217
|
+
*
|
|
218
|
+
* Functions like pTry use that.
|
|
219
|
+
*/
|
|
220
|
+
export type ErrorDataTuple<T = unknown, ERR = Error> = [err: null, data: T] | [err: ERR, data: null];
|
package/dist-esm/error/assert.js
CHANGED
|
@@ -68,6 +68,17 @@ export function _assertIsError(err, errorClass = Error) {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Asserts that passed object is indeed an Error of defined ErrorClass.
|
|
73
|
+
* If yes - returns peacefully (with TypeScript assertion).
|
|
74
|
+
* In not - throws (re-throws) that error up.
|
|
75
|
+
*/
|
|
76
|
+
export function _assertErrorClassOrRethrow(err, errorClass) {
|
|
77
|
+
if (!(err instanceof errorClass)) {
|
|
78
|
+
// re-throw
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
71
82
|
export function _assertIsErrorObject(obj) {
|
|
72
83
|
if (!_isErrorObject(obj)) {
|
|
73
84
|
const msg = [`expected to be ErrorObject`, `actual typeof: ${typeof obj}`].join('\n');
|
package/dist-esm/error/try.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _stringifyAny } from '../string/stringifyAny';
|
|
2
2
|
import { AppError } from './app.error';
|
|
3
|
+
import { _assertErrorClassOrRethrow } from './assert';
|
|
3
4
|
/**
|
|
4
5
|
* Calls a function, returns a Tuple of [error, value].
|
|
5
6
|
* Allows to write shorter code that avoids `try/catch`.
|
|
@@ -7,9 +8,6 @@ import { AppError } from './app.error';
|
|
|
7
8
|
*
|
|
8
9
|
* Similar to pTry, but for sync functions.
|
|
9
10
|
*
|
|
10
|
-
* For convenience, second argument type is non-optional,
|
|
11
|
-
* so you can use it without `!`. But you SHOULD always check `if (err)` first!
|
|
12
|
-
*
|
|
13
11
|
* ERR is typed as Error, not `unknown`. While unknown would be more correct,
|
|
14
12
|
* according to recent TypeScript, Error gives more developer convenience.
|
|
15
13
|
* In our code we NEVER throw non-errors.
|
|
@@ -22,26 +20,29 @@ import { AppError } from './app.error';
|
|
|
22
20
|
* if (err) ...do something...
|
|
23
21
|
* v // go ahead and use v
|
|
24
22
|
*/
|
|
25
|
-
export function _try(fn) {
|
|
23
|
+
export function _try(fn, errorClass) {
|
|
26
24
|
try {
|
|
27
25
|
return [null, fn()];
|
|
28
26
|
}
|
|
29
27
|
catch (err) {
|
|
30
|
-
|
|
28
|
+
if (errorClass) {
|
|
29
|
+
_assertErrorClassOrRethrow(err, errorClass);
|
|
30
|
+
}
|
|
31
|
+
return [err, null];
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* Like _try, but for Promises.
|
|
35
|
-
*
|
|
36
|
-
* Also, intentionally types second return item as non-optional,
|
|
37
|
-
* but you should check for `err` presense first!
|
|
38
36
|
*/
|
|
39
|
-
export async function pTry(promise) {
|
|
37
|
+
export async function pTry(promise, errorClass) {
|
|
40
38
|
try {
|
|
41
39
|
return [null, await promise];
|
|
42
40
|
}
|
|
43
41
|
catch (err) {
|
|
44
|
-
|
|
42
|
+
if (errorClass) {
|
|
43
|
+
_assertErrorClassOrRethrow(err, errorClass);
|
|
44
|
+
}
|
|
45
|
+
return [err, null];
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
@@ -106,5 +107,13 @@ export async function pExpectedError(promise, errorClass) {
|
|
|
106
107
|
* Shortcut function to simplify error snapshot-matching in tests.
|
|
107
108
|
*/
|
|
108
109
|
export async function pExpectedErrorString(promise, errorClass) {
|
|
109
|
-
|
|
110
|
+
const err = await pExpectedError(promise, errorClass);
|
|
111
|
+
return _stringifyAny(err);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Shortcut function to simplify error snapshot-matching in tests.
|
|
115
|
+
*/
|
|
116
|
+
export function _expectedErrorString(fn, errorClass) {
|
|
117
|
+
const err = _expectedError(fn, errorClass);
|
|
118
|
+
return _stringifyAny(err);
|
|
110
119
|
}
|
package/dist-esm/http/fetcher.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference lib="dom"/>
|
|
2
2
|
/// <reference lib="dom.iterable"/>
|
|
3
3
|
import { isServerSide } from '../env';
|
|
4
|
+
import { _assertErrorClassOrRethrow } from '../error/assert';
|
|
4
5
|
import { _anyToError, _anyToErrorObject, _errorLikeToErrorObject } from '../error/error.util';
|
|
5
6
|
import { HttpRequestError } from '../error/httpRequestError';
|
|
6
7
|
import { _clamp } from '../number/number.util';
|
|
@@ -25,6 +26,18 @@ const defRetryOptions = {
|
|
|
25
26
|
timeoutMax: 30000,
|
|
26
27
|
timeoutMultiplier: 2,
|
|
27
28
|
};
|
|
29
|
+
let globalFetcherMock = null;
|
|
30
|
+
/**
|
|
31
|
+
* Allows to mock Fetcher.callNativeFetch globally (for all Fetcher instances out there).
|
|
32
|
+
*
|
|
33
|
+
* Call it with `null` to reset/unmock.
|
|
34
|
+
*
|
|
35
|
+
* Please note that jest.spyOn(someFetcher, 'callNativeFetch') has higher priority than
|
|
36
|
+
* a global mock, so it'll run instead of a global mock.
|
|
37
|
+
*/
|
|
38
|
+
export function setGlobalFetcherMock(fn) {
|
|
39
|
+
globalFetcherMock = fn;
|
|
40
|
+
}
|
|
28
41
|
/**
|
|
29
42
|
* Experimental wrapper around Fetch.
|
|
30
43
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
|
@@ -96,10 +109,27 @@ export class Fetcher {
|
|
|
96
109
|
}
|
|
97
110
|
return res.body;
|
|
98
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Like pTry - returns a [err, data] tuple (aka ErrorDataTuple).
|
|
114
|
+
* err, if defined, is strictly HttpRequestError.
|
|
115
|
+
* UPD: actually not, err is typed as Error, as it feels unsafe to guarantee error type.
|
|
116
|
+
* UPD: actually yes - it will return HttpRequestError, and throw if there's an error
|
|
117
|
+
* of any other type.
|
|
118
|
+
*/
|
|
119
|
+
async tryFetch(opt) {
|
|
120
|
+
const res = await this.doFetch(opt);
|
|
121
|
+
if (res.err) {
|
|
122
|
+
_assertErrorClassOrRethrow(res.err, HttpRequestError);
|
|
123
|
+
return [res.err, null];
|
|
124
|
+
}
|
|
125
|
+
return [null, res.body];
|
|
126
|
+
}
|
|
99
127
|
/**
|
|
100
128
|
* Returns FetcherResponse.
|
|
101
129
|
* Never throws, returns `err` property in the response instead.
|
|
102
130
|
* Use this method instead of `throwHttpErrors: false` or try-catching.
|
|
131
|
+
*
|
|
132
|
+
* Note: responseType defaults to `void`, so, override it if you expect different.
|
|
103
133
|
*/
|
|
104
134
|
async doFetch(opt) {
|
|
105
135
|
var _a, _b;
|
|
@@ -252,6 +282,10 @@ export class Fetcher {
|
|
|
252
282
|
* This method exists to be able to easily mock it.
|
|
253
283
|
*/
|
|
254
284
|
async callNativeFetch(url, init) {
|
|
285
|
+
// If global mock is defined - call it instead of the native Fetch
|
|
286
|
+
if (globalFetcherMock) {
|
|
287
|
+
return await globalFetcherMock(url, init);
|
|
288
|
+
}
|
|
255
289
|
return await globalThis.fetch(url, init);
|
|
256
290
|
}
|
|
257
291
|
async onNotOkResponse(res) {
|
|
@@ -269,6 +303,11 @@ export class Fetcher {
|
|
|
269
303
|
cause = _anyToErrorObject(body);
|
|
270
304
|
}
|
|
271
305
|
}
|
|
306
|
+
cause || (cause = {
|
|
307
|
+
name: 'Error',
|
|
308
|
+
message: 'Fetch failed',
|
|
309
|
+
data: {},
|
|
310
|
+
});
|
|
272
311
|
const message = [(_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status, res.signature].filter(Boolean).join(' ');
|
|
273
312
|
res.err = new HttpRequestError(message, _filterNullishValues({
|
|
274
313
|
responseStatusCode: ((_b = res.fetchResponse) === null || _b === void 0 ? void 0 : _b.status) || 0,
|
|
@@ -431,7 +470,7 @@ export class Fetcher {
|
|
|
431
470
|
const norm = _merge({
|
|
432
471
|
baseUrl: '',
|
|
433
472
|
inputUrl: '',
|
|
434
|
-
responseType: '
|
|
473
|
+
responseType: 'json',
|
|
435
474
|
searchParams: {},
|
|
436
475
|
timeoutSeconds: 30,
|
|
437
476
|
retryPost: false,
|
package/package.json
CHANGED
package/src/error/assert.ts
CHANGED
|
@@ -102,6 +102,21 @@ export function _assertIsError<ERR extends Error = Error>(
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Asserts that passed object is indeed an Error of defined ErrorClass.
|
|
107
|
+
* If yes - returns peacefully (with TypeScript assertion).
|
|
108
|
+
* In not - throws (re-throws) that error up.
|
|
109
|
+
*/
|
|
110
|
+
export function _assertErrorClassOrRethrow<ERR extends Error>(
|
|
111
|
+
err: any,
|
|
112
|
+
errorClass: Class<ERR>,
|
|
113
|
+
): asserts err is ERR {
|
|
114
|
+
if (!(err instanceof errorClass)) {
|
|
115
|
+
// re-throw
|
|
116
|
+
throw err
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
export function _assertIsErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
|
|
106
121
|
obj: any,
|
|
107
122
|
): asserts obj is ErrorObject<DATA_TYPE> {
|
|
@@ -19,7 +19,14 @@ import type { ErrorObject, HttpRequestErrorData } from './error.model'
|
|
|
19
19
|
* (by default).
|
|
20
20
|
*/
|
|
21
21
|
export class HttpRequestError extends AppError<HttpRequestErrorData> {
|
|
22
|
-
constructor(message: string, data: HttpRequestErrorData, cause
|
|
22
|
+
constructor(message: string, data: HttpRequestErrorData, cause: ErrorObject) {
|
|
23
23
|
super(message, data, cause, 'HttpRequestError')
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Cause is strictly-defined for HttpRequestError,
|
|
28
|
+
* so it always has a cause.
|
|
29
|
+
* (for dev convenience)
|
|
30
|
+
*/
|
|
31
|
+
override cause!: ErrorObject
|
|
25
32
|
}
|
package/src/error/try.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { _stringifyAny } from '../string/stringifyAny'
|
|
2
2
|
import type { Class } from '../typeFest'
|
|
3
|
-
import type { AnyFunction } from '../types'
|
|
3
|
+
import type { AnyFunction, ErrorDataTuple } from '../types'
|
|
4
4
|
import { AppError } from './app.error'
|
|
5
|
+
import { _assertErrorClassOrRethrow } from './assert'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Calls a function, returns a Tuple of [error, value].
|
|
@@ -10,9 +11,6 @@ import { AppError } from './app.error'
|
|
|
10
11
|
*
|
|
11
12
|
* Similar to pTry, but for sync functions.
|
|
12
13
|
*
|
|
13
|
-
* For convenience, second argument type is non-optional,
|
|
14
|
-
* so you can use it without `!`. But you SHOULD always check `if (err)` first!
|
|
15
|
-
*
|
|
16
14
|
* ERR is typed as Error, not `unknown`. While unknown would be more correct,
|
|
17
15
|
* according to recent TypeScript, Error gives more developer convenience.
|
|
18
16
|
* In our code we NEVER throw non-errors.
|
|
@@ -25,29 +23,35 @@ import { AppError } from './app.error'
|
|
|
25
23
|
* if (err) ...do something...
|
|
26
24
|
* v // go ahead and use v
|
|
27
25
|
*/
|
|
28
|
-
export function _try<ERR
|
|
29
|
-
fn: () =>
|
|
30
|
-
|
|
26
|
+
export function _try<T, ERR extends Error = Error>(
|
|
27
|
+
fn: () => T,
|
|
28
|
+
errorClass?: Class<ERR>,
|
|
29
|
+
): ErrorDataTuple<T, ERR> {
|
|
31
30
|
try {
|
|
32
31
|
return [null, fn()]
|
|
33
32
|
} catch (err) {
|
|
34
|
-
|
|
33
|
+
if (errorClass) {
|
|
34
|
+
_assertErrorClassOrRethrow(err, errorClass)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return [err as ERR, null]
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* Like _try, but for Promises.
|
|
40
|
-
*
|
|
41
|
-
* Also, intentionally types second return item as non-optional,
|
|
42
|
-
* but you should check for `err` presense first!
|
|
43
43
|
*/
|
|
44
|
-
export async function pTry<ERR
|
|
45
|
-
promise: Promise<
|
|
46
|
-
|
|
44
|
+
export async function pTry<T, ERR extends Error = Error>(
|
|
45
|
+
promise: Promise<T>,
|
|
46
|
+
errorClass?: Class<ERR>,
|
|
47
|
+
): Promise<ErrorDataTuple<Awaited<T>, ERR>> {
|
|
47
48
|
try {
|
|
48
49
|
return [null, await promise]
|
|
49
50
|
} catch (err) {
|
|
50
|
-
|
|
51
|
+
if (errorClass) {
|
|
52
|
+
_assertErrorClassOrRethrow(err, errorClass)
|
|
53
|
+
}
|
|
54
|
+
return [err as ERR, null]
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -122,5 +126,17 @@ export async function pExpectedErrorString<ERR = Error>(
|
|
|
122
126
|
promise: Promise<any>,
|
|
123
127
|
errorClass?: Class<ERR>,
|
|
124
128
|
): Promise<string> {
|
|
125
|
-
|
|
129
|
+
const err = await pExpectedError<ERR>(promise, errorClass)
|
|
130
|
+
return _stringifyAny(err)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Shortcut function to simplify error snapshot-matching in tests.
|
|
135
|
+
*/
|
|
136
|
+
export function _expectedErrorString<ERR = Error>(
|
|
137
|
+
fn: AnyFunction,
|
|
138
|
+
errorClass?: Class<ERR>,
|
|
139
|
+
): string {
|
|
140
|
+
const err = _expectedError<ERR>(fn, errorClass)
|
|
141
|
+
return _stringifyAny(err)
|
|
126
142
|
}
|
|
@@ -186,7 +186,7 @@ export interface FetcherOptions {
|
|
|
186
186
|
// init?: Partial<RequestInitNormalized>
|
|
187
187
|
|
|
188
188
|
headers?: Record<string, any>
|
|
189
|
-
responseType?: FetcherResponseType // default to '
|
|
189
|
+
responseType?: FetcherResponseType // default to 'json'
|
|
190
190
|
|
|
191
191
|
searchParams?: Record<string, any>
|
|
192
192
|
|
|
@@ -266,3 +266,10 @@ export type FetcherResponseType =
|
|
|
266
266
|
| 'arrayBuffer'
|
|
267
267
|
| 'blob'
|
|
268
268
|
| 'readableStream'
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Function that mocks `Fetcher.callNativeFetch` globally.
|
|
272
|
+
*
|
|
273
|
+
* url is `fullUrl` (includes baseUrl and all).
|
|
274
|
+
*/
|
|
275
|
+
export type FetcherMockFunction = (url: string, init: RequestInitNormalized) => Promise<Response>
|
package/src/http/fetcher.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/// <reference lib="dom.iterable"/>
|
|
3
3
|
|
|
4
4
|
import { isServerSide } from '../env'
|
|
5
|
+
import { _assertErrorClassOrRethrow } from '../error/assert'
|
|
5
6
|
import { ErrorLike, ErrorObject } from '../error/error.model'
|
|
6
7
|
import { _anyToError, _anyToErrorObject, _errorLikeToErrorObject } from '../error/error.util'
|
|
7
8
|
import { HttpRequestError } from '../error/httpRequestError'
|
|
@@ -19,7 +20,7 @@ import { pTimeout, TimeoutError } from '../promise/pTimeout'
|
|
|
19
20
|
import { _jsonParse, _jsonParseIfPossible } from '../string/json.util'
|
|
20
21
|
import { _stringifyAny } from '../string/stringifyAny'
|
|
21
22
|
import { _ms, _since } from '../time/time.util'
|
|
22
|
-
import { NumberOfMilliseconds } from '../types'
|
|
23
|
+
import { ErrorDataTuple, NumberOfMilliseconds } from '../types'
|
|
23
24
|
import type {
|
|
24
25
|
FetcherAfterResponseHook,
|
|
25
26
|
FetcherBeforeRequestHook,
|
|
@@ -31,6 +32,8 @@ import type {
|
|
|
31
32
|
FetcherResponse,
|
|
32
33
|
FetcherResponseType,
|
|
33
34
|
FetcherRetryOptions,
|
|
35
|
+
FetcherMockFunction,
|
|
36
|
+
RequestInitNormalized,
|
|
34
37
|
} from './fetcher.model'
|
|
35
38
|
import { HTTP_METHODS } from './http.model'
|
|
36
39
|
import type { HttpStatusFamily } from './http.model'
|
|
@@ -51,6 +54,20 @@ const defRetryOptions: FetcherRetryOptions = {
|
|
|
51
54
|
timeoutMultiplier: 2,
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
let globalFetcherMock: FetcherMockFunction | null = null
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Allows to mock Fetcher.callNativeFetch globally (for all Fetcher instances out there).
|
|
61
|
+
*
|
|
62
|
+
* Call it with `null` to reset/unmock.
|
|
63
|
+
*
|
|
64
|
+
* Please note that jest.spyOn(someFetcher, 'callNativeFetch') has higher priority than
|
|
65
|
+
* a global mock, so it'll run instead of a global mock.
|
|
66
|
+
*/
|
|
67
|
+
export function setGlobalFetcherMock(fn: FetcherMockFunction | null): void {
|
|
68
|
+
globalFetcherMock = fn
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
/**
|
|
55
72
|
* Experimental wrapper around Fetch.
|
|
56
73
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
|
@@ -169,10 +186,28 @@ export class Fetcher {
|
|
|
169
186
|
return res.body
|
|
170
187
|
}
|
|
171
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Like pTry - returns a [err, data] tuple (aka ErrorDataTuple).
|
|
191
|
+
* err, if defined, is strictly HttpRequestError.
|
|
192
|
+
* UPD: actually not, err is typed as Error, as it feels unsafe to guarantee error type.
|
|
193
|
+
* UPD: actually yes - it will return HttpRequestError, and throw if there's an error
|
|
194
|
+
* of any other type.
|
|
195
|
+
*/
|
|
196
|
+
async tryFetch<T = unknown>(opt: FetcherOptions): Promise<ErrorDataTuple<T, HttpRequestError>> {
|
|
197
|
+
const res = await this.doFetch<T>(opt)
|
|
198
|
+
if (res.err) {
|
|
199
|
+
_assertErrorClassOrRethrow(res.err, HttpRequestError)
|
|
200
|
+
return [res.err, null]
|
|
201
|
+
}
|
|
202
|
+
return [null, res.body]
|
|
203
|
+
}
|
|
204
|
+
|
|
172
205
|
/**
|
|
173
206
|
* Returns FetcherResponse.
|
|
174
207
|
* Never throws, returns `err` property in the response instead.
|
|
175
208
|
* Use this method instead of `throwHttpErrors: false` or try-catching.
|
|
209
|
+
*
|
|
210
|
+
* Note: responseType defaults to `void`, so, override it if you expect different.
|
|
176
211
|
*/
|
|
177
212
|
async doFetch<T = unknown>(opt: FetcherOptions): Promise<FetcherResponse<T>> {
|
|
178
213
|
const req = this.normalizeOptions(opt)
|
|
@@ -344,12 +379,17 @@ export class Fetcher {
|
|
|
344
379
|
/**
|
|
345
380
|
* This method exists to be able to easily mock it.
|
|
346
381
|
*/
|
|
347
|
-
async callNativeFetch(url: string, init:
|
|
382
|
+
async callNativeFetch(url: string, init: RequestInitNormalized): Promise<Response> {
|
|
383
|
+
// If global mock is defined - call it instead of the native Fetch
|
|
384
|
+
if (globalFetcherMock) {
|
|
385
|
+
return await globalFetcherMock(url, init)
|
|
386
|
+
}
|
|
387
|
+
|
|
348
388
|
return await globalThis.fetch(url, init)
|
|
349
389
|
}
|
|
350
390
|
|
|
351
391
|
private async onNotOkResponse(res: FetcherResponse): Promise<void> {
|
|
352
|
-
let cause: ErrorObject
|
|
392
|
+
let cause: ErrorObject
|
|
353
393
|
|
|
354
394
|
if (res.err) {
|
|
355
395
|
// This is only possible on JSON.parse error, or CORS error,
|
|
@@ -363,6 +403,12 @@ export class Fetcher {
|
|
|
363
403
|
}
|
|
364
404
|
}
|
|
365
405
|
|
|
406
|
+
cause ||= {
|
|
407
|
+
name: 'Error',
|
|
408
|
+
message: 'Fetch failed',
|
|
409
|
+
data: {},
|
|
410
|
+
}
|
|
411
|
+
|
|
366
412
|
const message = [res.fetchResponse?.status, res.signature].filter(Boolean).join(' ')
|
|
367
413
|
|
|
368
414
|
res.err = new HttpRequestError(
|
|
@@ -545,7 +591,7 @@ export class Fetcher {
|
|
|
545
591
|
{
|
|
546
592
|
baseUrl: '',
|
|
547
593
|
inputUrl: '',
|
|
548
|
-
responseType: '
|
|
594
|
+
responseType: 'json',
|
|
549
595
|
searchParams: {},
|
|
550
596
|
timeoutSeconds: 30,
|
|
551
597
|
retryPost: false,
|
package/src/types.ts
CHANGED
|
@@ -278,3 +278,15 @@ export const _objectAssign = Object.assign as <T extends AnyObject>(
|
|
|
278
278
|
target: T,
|
|
279
279
|
part: Partial<T>,
|
|
280
280
|
) => T
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Defines a tuple of [err, data]
|
|
284
|
+
* where only 1 of them exists.
|
|
285
|
+
* Either error exists and data is null
|
|
286
|
+
* Or error is null and data is defined.
|
|
287
|
+
* This forces you to check `if (err)`, which lets
|
|
288
|
+
* TypeScript infer the existence of `data`.
|
|
289
|
+
*
|
|
290
|
+
* Functions like pTry use that.
|
|
291
|
+
*/
|
|
292
|
+
export type ErrorDataTuple<T = unknown, ERR = Error> = [err: null, data: T] | [err: ERR, data: null]
|