@naturalcycles/js-lib 14.126.0 → 14.127.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/http/fetcher.d.ts +1 -148
- package/dist/http/fetcher.js +3 -0
- package/dist/http/fetcher.model.d.ts +151 -0
- package/dist/http/fetcher.model.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist-esm/http/fetcher.js +3 -0
- package/dist-esm/http/fetcher.model.js +1 -0
- package/dist-esm/index.js +1 -0
- package/package.json +1 -1
- package/src/http/fetcher.model.ts +181 -0
- package/src/http/fetcher.ts +18 -179
- package/src/index.ts +1 -0
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -1,152 +1,5 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
-
import {
|
|
3
|
-
import type { Promisable } from '../typeFest';
|
|
4
|
-
import { Reviver } from '../types';
|
|
5
|
-
import type { HttpMethod, HttpStatusFamily } from './http.model';
|
|
6
|
-
export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
|
|
7
|
-
logger: CommonLogger;
|
|
8
|
-
searchParams: Record<string, any>;
|
|
9
|
-
}
|
|
10
|
-
export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>;
|
|
11
|
-
export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>;
|
|
12
|
-
export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>;
|
|
13
|
-
export interface FetcherCfg {
|
|
14
|
-
/**
|
|
15
|
-
* Should **not** contain trailing slash.
|
|
16
|
-
*/
|
|
17
|
-
baseUrl?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Default rule is that you **are allowed** to mutate req, res, res.retryStatus
|
|
20
|
-
* properties of hook function arguments.
|
|
21
|
-
* If you throw an error from the hook - it will be re-thrown as-is.
|
|
22
|
-
*/
|
|
23
|
-
hooks?: {
|
|
24
|
-
/**
|
|
25
|
-
* Allows to mutate req.
|
|
26
|
-
*/
|
|
27
|
-
beforeRequest?: FetcherBeforeRequestHook[];
|
|
28
|
-
/**
|
|
29
|
-
* Allows to mutate res.
|
|
30
|
-
* If you set `res.err` - it will be thrown.
|
|
31
|
-
*/
|
|
32
|
-
afterResponse?: FetcherAfterResponseHook[];
|
|
33
|
-
/**
|
|
34
|
-
* Allows to mutate res.retryStatus to override retry behavior.
|
|
35
|
-
*/
|
|
36
|
-
beforeRetry?: FetcherBeforeRetryHook[];
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* If true - enables all possible logging.
|
|
40
|
-
*/
|
|
41
|
-
debug?: boolean;
|
|
42
|
-
logRequest?: boolean;
|
|
43
|
-
logRequestBody?: boolean;
|
|
44
|
-
logResponse?: boolean;
|
|
45
|
-
logResponseBody?: boolean;
|
|
46
|
-
/**
|
|
47
|
-
* Default to true.
|
|
48
|
-
* Set to false to exclude `prefixUrl` from logs (both success and error)
|
|
49
|
-
*/
|
|
50
|
-
logWithPrefixUrl?: boolean;
|
|
51
|
-
/**
|
|
52
|
-
* Default to true.
|
|
53
|
-
* Set to false to strip searchParams from url when logging (both success and error)
|
|
54
|
-
*/
|
|
55
|
-
logWithSearchParams?: boolean;
|
|
56
|
-
/**
|
|
57
|
-
* Defaults to `console`.
|
|
58
|
-
*/
|
|
59
|
-
logger?: CommonLogger;
|
|
60
|
-
}
|
|
61
|
-
export interface FetcherRetryStatus {
|
|
62
|
-
retryAttempt: number;
|
|
63
|
-
retryTimeout: number;
|
|
64
|
-
retryStopped: boolean;
|
|
65
|
-
}
|
|
66
|
-
export interface FetcherRetryOptions {
|
|
67
|
-
count: number;
|
|
68
|
-
timeout: number;
|
|
69
|
-
timeoutMax: number;
|
|
70
|
-
timeoutMultiplier: number;
|
|
71
|
-
}
|
|
72
|
-
export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
|
|
73
|
-
url: string;
|
|
74
|
-
init: RequestInitNormalized;
|
|
75
|
-
mode: FetcherMode;
|
|
76
|
-
throwHttpErrors: boolean;
|
|
77
|
-
timeoutSeconds: number;
|
|
78
|
-
retry: FetcherRetryOptions;
|
|
79
|
-
retryPost: boolean;
|
|
80
|
-
retry4xx: boolean;
|
|
81
|
-
retry5xx: boolean;
|
|
82
|
-
}
|
|
83
|
-
export interface FetcherOptions {
|
|
84
|
-
method?: HttpMethod;
|
|
85
|
-
throwHttpErrors?: boolean;
|
|
86
|
-
/**
|
|
87
|
-
* Default: 30.
|
|
88
|
-
*
|
|
89
|
-
* Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
|
|
90
|
-
* so both should finish within this single timeout (not each).
|
|
91
|
-
*/
|
|
92
|
-
timeoutSeconds?: number;
|
|
93
|
-
json?: any;
|
|
94
|
-
text?: string;
|
|
95
|
-
/**
|
|
96
|
-
* Supports all the types that RequestInit.body supports.
|
|
97
|
-
*
|
|
98
|
-
* Useful when you want to e.g pass FormData.
|
|
99
|
-
*/
|
|
100
|
-
body?: Blob | BufferSource | FormData | URLSearchParams | string;
|
|
101
|
-
credentials?: RequestCredentials;
|
|
102
|
-
/**
|
|
103
|
-
* Default to true.
|
|
104
|
-
*/
|
|
105
|
-
followRedirects?: boolean;
|
|
106
|
-
headers?: Record<string, any>;
|
|
107
|
-
mode?: FetcherMode;
|
|
108
|
-
searchParams?: Record<string, any>;
|
|
109
|
-
/**
|
|
110
|
-
* Default is 2 retries (3 tries in total).
|
|
111
|
-
* Pass `retry: { count: 0 }` to disable retries.
|
|
112
|
-
*/
|
|
113
|
-
retry?: Partial<FetcherRetryOptions>;
|
|
114
|
-
/**
|
|
115
|
-
* Defaults to false.
|
|
116
|
-
* Set to true to allow retrying `post` requests.
|
|
117
|
-
*/
|
|
118
|
-
retryPost?: boolean;
|
|
119
|
-
/**
|
|
120
|
-
* Defaults to false.
|
|
121
|
-
*/
|
|
122
|
-
retry4xx?: boolean;
|
|
123
|
-
/**
|
|
124
|
-
* Defaults to true.
|
|
125
|
-
*/
|
|
126
|
-
retry5xx?: boolean;
|
|
127
|
-
jsonReviver?: Reviver;
|
|
128
|
-
}
|
|
129
|
-
export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
|
|
130
|
-
method: HttpMethod;
|
|
131
|
-
headers: Record<string, any>;
|
|
132
|
-
};
|
|
133
|
-
export interface FetcherSuccessResponse<BODY = unknown> extends FetcherResponse<BODY> {
|
|
134
|
-
err?: undefined;
|
|
135
|
-
fetchResponse: Response;
|
|
136
|
-
body: BODY;
|
|
137
|
-
}
|
|
138
|
-
export interface FetcherErrorResponse<BODY = unknown> extends FetcherResponse<BODY> {
|
|
139
|
-
err: Error;
|
|
140
|
-
}
|
|
141
|
-
export interface FetcherResponse<BODY = unknown> {
|
|
142
|
-
err?: Error;
|
|
143
|
-
req: FetcherRequest;
|
|
144
|
-
fetchResponse?: Response;
|
|
145
|
-
statusFamily?: HttpStatusFamily;
|
|
146
|
-
body?: BODY;
|
|
147
|
-
retryStatus: FetcherRetryStatus;
|
|
148
|
-
}
|
|
149
|
-
export type FetcherMode = 'json' | 'text' | 'void';
|
|
2
|
+
import type { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherNormalizedCfg, FetcherOptions, FetcherResponse } from './fetcher.model';
|
|
150
3
|
/**
|
|
151
4
|
* Experimental wrapper around Fetch.
|
|
152
5
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
package/dist/http/fetcher.js
CHANGED
|
@@ -133,10 +133,12 @@ class Fetcher {
|
|
|
133
133
|
}
|
|
134
134
|
try {
|
|
135
135
|
res.fetchResponse = await globalThis.fetch(req.url, req.init);
|
|
136
|
+
res.ok = res.fetchResponse.ok;
|
|
136
137
|
}
|
|
137
138
|
catch (err) {
|
|
138
139
|
// For example, CORS error would result in "TypeError: failed to fetch" here
|
|
139
140
|
res.err = err;
|
|
141
|
+
res.ok = false;
|
|
140
142
|
}
|
|
141
143
|
res.statusFamily = this.getStatusFamily(res);
|
|
142
144
|
if (res.fetchResponse?.ok) {
|
|
@@ -148,6 +150,7 @@ class Fetcher {
|
|
|
148
150
|
res.body = JSON.parse(text, req.jsonReviver);
|
|
149
151
|
}
|
|
150
152
|
catch (err) {
|
|
153
|
+
res.ok = false;
|
|
151
154
|
res.err = (0, error_util_1._anyToError)(err, http_error_1.HttpError, (0, object_util_1._filterNullishValues)({
|
|
152
155
|
httpStatusCode: 0,
|
|
153
156
|
url: req.url,
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { CommonLogger } from '../log/commonLogger';
|
|
2
|
+
import type { Promisable } from '../typeFest';
|
|
3
|
+
import type { Reviver } from '../types';
|
|
4
|
+
import type { HttpMethod, HttpStatusFamily } from './http.model';
|
|
5
|
+
export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
|
|
6
|
+
logger: CommonLogger;
|
|
7
|
+
searchParams: Record<string, any>;
|
|
8
|
+
}
|
|
9
|
+
export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>;
|
|
10
|
+
export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>;
|
|
11
|
+
export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>;
|
|
12
|
+
export interface FetcherCfg {
|
|
13
|
+
/**
|
|
14
|
+
* Should **not** contain trailing slash.
|
|
15
|
+
*/
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Default rule is that you **are allowed** to mutate req, res, res.retryStatus
|
|
19
|
+
* properties of hook function arguments.
|
|
20
|
+
* If you throw an error from the hook - it will be re-thrown as-is.
|
|
21
|
+
*/
|
|
22
|
+
hooks?: {
|
|
23
|
+
/**
|
|
24
|
+
* Allows to mutate req.
|
|
25
|
+
*/
|
|
26
|
+
beforeRequest?: FetcherBeforeRequestHook[];
|
|
27
|
+
/**
|
|
28
|
+
* Allows to mutate res.
|
|
29
|
+
* If you set `res.err` - it will be thrown.
|
|
30
|
+
*/
|
|
31
|
+
afterResponse?: FetcherAfterResponseHook[];
|
|
32
|
+
/**
|
|
33
|
+
* Allows to mutate res.retryStatus to override retry behavior.
|
|
34
|
+
*/
|
|
35
|
+
beforeRetry?: FetcherBeforeRetryHook[];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* If true - enables all possible logging.
|
|
39
|
+
*/
|
|
40
|
+
debug?: boolean;
|
|
41
|
+
logRequest?: boolean;
|
|
42
|
+
logRequestBody?: boolean;
|
|
43
|
+
logResponse?: boolean;
|
|
44
|
+
logResponseBody?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Default to true.
|
|
47
|
+
* Set to false to exclude `prefixUrl` from logs (both success and error)
|
|
48
|
+
*/
|
|
49
|
+
logWithPrefixUrl?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Default to true.
|
|
52
|
+
* Set to false to strip searchParams from url when logging (both success and error)
|
|
53
|
+
*/
|
|
54
|
+
logWithSearchParams?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Defaults to `console`.
|
|
57
|
+
*/
|
|
58
|
+
logger?: CommonLogger;
|
|
59
|
+
}
|
|
60
|
+
export interface FetcherRetryStatus {
|
|
61
|
+
retryAttempt: number;
|
|
62
|
+
retryTimeout: number;
|
|
63
|
+
retryStopped: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface FetcherRetryOptions {
|
|
66
|
+
count: number;
|
|
67
|
+
timeout: number;
|
|
68
|
+
timeoutMax: number;
|
|
69
|
+
timeoutMultiplier: number;
|
|
70
|
+
}
|
|
71
|
+
export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
|
|
72
|
+
url: string;
|
|
73
|
+
init: RequestInitNormalized;
|
|
74
|
+
mode: FetcherMode;
|
|
75
|
+
throwHttpErrors: boolean;
|
|
76
|
+
timeoutSeconds: number;
|
|
77
|
+
retry: FetcherRetryOptions;
|
|
78
|
+
retryPost: boolean;
|
|
79
|
+
retry4xx: boolean;
|
|
80
|
+
retry5xx: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface FetcherOptions {
|
|
83
|
+
method?: HttpMethod;
|
|
84
|
+
throwHttpErrors?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Default: 30.
|
|
87
|
+
*
|
|
88
|
+
* Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
|
|
89
|
+
* so both should finish within this single timeout (not each).
|
|
90
|
+
*/
|
|
91
|
+
timeoutSeconds?: number;
|
|
92
|
+
json?: any;
|
|
93
|
+
text?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Supports all the types that RequestInit.body supports.
|
|
96
|
+
*
|
|
97
|
+
* Useful when you want to e.g pass FormData.
|
|
98
|
+
*/
|
|
99
|
+
body?: Blob | BufferSource | FormData | URLSearchParams | string;
|
|
100
|
+
credentials?: RequestCredentials;
|
|
101
|
+
/**
|
|
102
|
+
* Default to true.
|
|
103
|
+
*/
|
|
104
|
+
followRedirects?: boolean;
|
|
105
|
+
headers?: Record<string, any>;
|
|
106
|
+
mode?: FetcherMode;
|
|
107
|
+
searchParams?: Record<string, any>;
|
|
108
|
+
/**
|
|
109
|
+
* Default is 2 retries (3 tries in total).
|
|
110
|
+
* Pass `retry: { count: 0 }` to disable retries.
|
|
111
|
+
*/
|
|
112
|
+
retry?: Partial<FetcherRetryOptions>;
|
|
113
|
+
/**
|
|
114
|
+
* Defaults to false.
|
|
115
|
+
* Set to true to allow retrying `post` requests.
|
|
116
|
+
*/
|
|
117
|
+
retryPost?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Defaults to false.
|
|
120
|
+
*/
|
|
121
|
+
retry4xx?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Defaults to true.
|
|
124
|
+
*/
|
|
125
|
+
retry5xx?: boolean;
|
|
126
|
+
jsonReviver?: Reviver;
|
|
127
|
+
}
|
|
128
|
+
export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
|
|
129
|
+
method: HttpMethod;
|
|
130
|
+
headers: Record<string, any>;
|
|
131
|
+
};
|
|
132
|
+
export interface FetcherSuccessResponse<BODY = unknown> {
|
|
133
|
+
ok: true;
|
|
134
|
+
err: undefined;
|
|
135
|
+
fetchResponse: Response;
|
|
136
|
+
body: BODY;
|
|
137
|
+
req: FetcherRequest;
|
|
138
|
+
statusFamily?: HttpStatusFamily;
|
|
139
|
+
retryStatus: FetcherRetryStatus;
|
|
140
|
+
}
|
|
141
|
+
export interface FetcherErrorResponse<BODY = unknown> {
|
|
142
|
+
ok: false;
|
|
143
|
+
err: Error;
|
|
144
|
+
fetchResponse?: Response;
|
|
145
|
+
body?: BODY;
|
|
146
|
+
req: FetcherRequest;
|
|
147
|
+
statusFamily?: HttpStatusFamily;
|
|
148
|
+
retryStatus: FetcherRetryStatus;
|
|
149
|
+
}
|
|
150
|
+
export type FetcherResponse<BODY = unknown> = FetcherSuccessResponse<BODY> | FetcherErrorResponse<BODY>;
|
|
151
|
+
export type FetcherMode = 'json' | 'text' | 'void';
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -81,3 +81,4 @@ tslib_1.__exportStar(require("./datetime/dateInterval"), exports);
|
|
|
81
81
|
tslib_1.__exportStar(require("./datetime/timeInterval"), exports);
|
|
82
82
|
tslib_1.__exportStar(require("./http/http.model"), exports);
|
|
83
83
|
tslib_1.__exportStar(require("./http/fetcher"), exports);
|
|
84
|
+
tslib_1.__exportStar(require("./http/fetcher.model"), exports);
|
package/dist-esm/http/fetcher.js
CHANGED
|
@@ -141,10 +141,12 @@ export class Fetcher {
|
|
|
141
141
|
}
|
|
142
142
|
try {
|
|
143
143
|
res.fetchResponse = await globalThis.fetch(req.url, req.init);
|
|
144
|
+
res.ok = res.fetchResponse.ok;
|
|
144
145
|
}
|
|
145
146
|
catch (err) {
|
|
146
147
|
// For example, CORS error would result in "TypeError: failed to fetch" here
|
|
147
148
|
res.err = err;
|
|
149
|
+
res.ok = false;
|
|
148
150
|
}
|
|
149
151
|
res.statusFamily = this.getStatusFamily(res);
|
|
150
152
|
if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
|
|
@@ -156,6 +158,7 @@ export class Fetcher {
|
|
|
156
158
|
res.body = JSON.parse(text, req.jsonReviver);
|
|
157
159
|
}
|
|
158
160
|
catch (err) {
|
|
161
|
+
res.ok = false;
|
|
159
162
|
res.err = _anyToError(err, HttpError, _filterNullishValues({
|
|
160
163
|
httpStatusCode: 0,
|
|
161
164
|
url: req.url,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist-esm/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { CommonLogger } from '../log/commonLogger'
|
|
2
|
+
import type { Promisable } from '../typeFest'
|
|
3
|
+
import type { Reviver } from '../types'
|
|
4
|
+
import type { HttpMethod, HttpStatusFamily } from './http.model'
|
|
5
|
+
|
|
6
|
+
export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
|
|
7
|
+
logger: CommonLogger
|
|
8
|
+
searchParams: Record<string, any>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>
|
|
12
|
+
export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>
|
|
13
|
+
export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>
|
|
14
|
+
|
|
15
|
+
export interface FetcherCfg {
|
|
16
|
+
/**
|
|
17
|
+
* Should **not** contain trailing slash.
|
|
18
|
+
*/
|
|
19
|
+
baseUrl?: string
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default rule is that you **are allowed** to mutate req, res, res.retryStatus
|
|
23
|
+
* properties of hook function arguments.
|
|
24
|
+
* If you throw an error from the hook - it will be re-thrown as-is.
|
|
25
|
+
*/
|
|
26
|
+
hooks?: {
|
|
27
|
+
/**
|
|
28
|
+
* Allows to mutate req.
|
|
29
|
+
*/
|
|
30
|
+
beforeRequest?: FetcherBeforeRequestHook[]
|
|
31
|
+
/**
|
|
32
|
+
* Allows to mutate res.
|
|
33
|
+
* If you set `res.err` - it will be thrown.
|
|
34
|
+
*/
|
|
35
|
+
afterResponse?: FetcherAfterResponseHook[]
|
|
36
|
+
/**
|
|
37
|
+
* Allows to mutate res.retryStatus to override retry behavior.
|
|
38
|
+
*/
|
|
39
|
+
beforeRetry?: FetcherBeforeRetryHook[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* If true - enables all possible logging.
|
|
44
|
+
*/
|
|
45
|
+
debug?: boolean
|
|
46
|
+
logRequest?: boolean
|
|
47
|
+
logRequestBody?: boolean
|
|
48
|
+
logResponse?: boolean
|
|
49
|
+
logResponseBody?: boolean
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default to true.
|
|
53
|
+
* Set to false to exclude `prefixUrl` from logs (both success and error)
|
|
54
|
+
*/
|
|
55
|
+
logWithPrefixUrl?: boolean
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Default to true.
|
|
59
|
+
* Set to false to strip searchParams from url when logging (both success and error)
|
|
60
|
+
*/
|
|
61
|
+
logWithSearchParams?: boolean
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Defaults to `console`.
|
|
65
|
+
*/
|
|
66
|
+
logger?: CommonLogger
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface FetcherRetryStatus {
|
|
70
|
+
retryAttempt: number
|
|
71
|
+
retryTimeout: number
|
|
72
|
+
retryStopped: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface FetcherRetryOptions {
|
|
76
|
+
count: number
|
|
77
|
+
timeout: number
|
|
78
|
+
timeoutMax: number
|
|
79
|
+
timeoutMultiplier: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
|
|
83
|
+
url: string
|
|
84
|
+
init: RequestInitNormalized
|
|
85
|
+
mode: FetcherMode
|
|
86
|
+
throwHttpErrors: boolean
|
|
87
|
+
timeoutSeconds: number
|
|
88
|
+
retry: FetcherRetryOptions
|
|
89
|
+
retryPost: boolean
|
|
90
|
+
retry4xx: boolean
|
|
91
|
+
retry5xx: boolean
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface FetcherOptions {
|
|
95
|
+
method?: HttpMethod
|
|
96
|
+
throwHttpErrors?: boolean
|
|
97
|
+
/**
|
|
98
|
+
* Default: 30.
|
|
99
|
+
*
|
|
100
|
+
* Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
|
|
101
|
+
* so both should finish within this single timeout (not each).
|
|
102
|
+
*/
|
|
103
|
+
timeoutSeconds?: number
|
|
104
|
+
|
|
105
|
+
json?: any
|
|
106
|
+
text?: string
|
|
107
|
+
/**
|
|
108
|
+
* Supports all the types that RequestInit.body supports.
|
|
109
|
+
*
|
|
110
|
+
* Useful when you want to e.g pass FormData.
|
|
111
|
+
*/
|
|
112
|
+
body?: Blob | BufferSource | FormData | URLSearchParams | string
|
|
113
|
+
|
|
114
|
+
credentials?: RequestCredentials
|
|
115
|
+
/**
|
|
116
|
+
* Default to true.
|
|
117
|
+
*/
|
|
118
|
+
followRedirects?: boolean
|
|
119
|
+
|
|
120
|
+
// Removing RequestInit from options to simplify FetcherOptions interface.
|
|
121
|
+
// Will instead only add hand-picked useful options, such as `credentials`.
|
|
122
|
+
// init?: Partial<RequestInitNormalized>
|
|
123
|
+
|
|
124
|
+
headers?: Record<string, any>
|
|
125
|
+
mode?: FetcherMode // default to 'void'
|
|
126
|
+
|
|
127
|
+
searchParams?: Record<string, any>
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Default is 2 retries (3 tries in total).
|
|
131
|
+
* Pass `retry: { count: 0 }` to disable retries.
|
|
132
|
+
*/
|
|
133
|
+
retry?: Partial<FetcherRetryOptions>
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Defaults to false.
|
|
137
|
+
* Set to true to allow retrying `post` requests.
|
|
138
|
+
*/
|
|
139
|
+
retryPost?: boolean
|
|
140
|
+
/**
|
|
141
|
+
* Defaults to false.
|
|
142
|
+
*/
|
|
143
|
+
retry4xx?: boolean
|
|
144
|
+
/**
|
|
145
|
+
* Defaults to true.
|
|
146
|
+
*/
|
|
147
|
+
retry5xx?: boolean
|
|
148
|
+
|
|
149
|
+
jsonReviver?: Reviver
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
|
|
153
|
+
method: HttpMethod
|
|
154
|
+
headers: Record<string, any>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface FetcherSuccessResponse<BODY = unknown> {
|
|
158
|
+
ok: true
|
|
159
|
+
err: undefined
|
|
160
|
+
fetchResponse: Response
|
|
161
|
+
body: BODY
|
|
162
|
+
req: FetcherRequest
|
|
163
|
+
statusFamily?: HttpStatusFamily
|
|
164
|
+
retryStatus: FetcherRetryStatus
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface FetcherErrorResponse<BODY = unknown> {
|
|
168
|
+
ok: false
|
|
169
|
+
err: Error
|
|
170
|
+
fetchResponse?: Response
|
|
171
|
+
body?: BODY
|
|
172
|
+
req: FetcherRequest
|
|
173
|
+
statusFamily?: HttpStatusFamily
|
|
174
|
+
retryStatus: FetcherRetryStatus
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type FetcherResponse<BODY = unknown> =
|
|
178
|
+
| FetcherSuccessResponse<BODY>
|
|
179
|
+
| FetcherErrorResponse<BODY>
|
|
180
|
+
|
|
181
|
+
export type FetcherMode = 'json' | 'text' | 'void'
|
package/src/http/fetcher.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { ErrorObject } from '../error/error.model'
|
|
4
4
|
import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
|
|
5
5
|
import { HttpError } from '../error/http.error'
|
|
6
|
-
import { CommonLogger } from '../log/commonLogger'
|
|
7
6
|
import { _clamp } from '../number/number.util'
|
|
8
7
|
import {
|
|
9
8
|
_filterNullishValues,
|
|
@@ -15,182 +14,19 @@ import {
|
|
|
15
14
|
import { pDelay } from '../promise/pDelay'
|
|
16
15
|
import { _jsonParseIfPossible } from '../string/json.util'
|
|
17
16
|
import { _since } from '../time/time.util'
|
|
18
|
-
import type {
|
|
19
|
-
|
|
17
|
+
import type {
|
|
18
|
+
FetcherAfterResponseHook,
|
|
19
|
+
FetcherBeforeRequestHook,
|
|
20
|
+
FetcherBeforeRetryHook,
|
|
21
|
+
FetcherCfg,
|
|
22
|
+
FetcherNormalizedCfg,
|
|
23
|
+
FetcherOptions,
|
|
24
|
+
FetcherRequest,
|
|
25
|
+
FetcherResponse,
|
|
26
|
+
FetcherRetryOptions,
|
|
27
|
+
} from './fetcher.model'
|
|
20
28
|
import { HTTP_METHODS } from './http.model'
|
|
21
|
-
import type {
|
|
22
|
-
|
|
23
|
-
export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
|
|
24
|
-
logger: CommonLogger
|
|
25
|
-
searchParams: Record<string, any>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>
|
|
29
|
-
export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>
|
|
30
|
-
export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>
|
|
31
|
-
|
|
32
|
-
export interface FetcherCfg {
|
|
33
|
-
/**
|
|
34
|
-
* Should **not** contain trailing slash.
|
|
35
|
-
*/
|
|
36
|
-
baseUrl?: string
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Default rule is that you **are allowed** to mutate req, res, res.retryStatus
|
|
40
|
-
* properties of hook function arguments.
|
|
41
|
-
* If you throw an error from the hook - it will be re-thrown as-is.
|
|
42
|
-
*/
|
|
43
|
-
hooks?: {
|
|
44
|
-
/**
|
|
45
|
-
* Allows to mutate req.
|
|
46
|
-
*/
|
|
47
|
-
beforeRequest?: FetcherBeforeRequestHook[]
|
|
48
|
-
/**
|
|
49
|
-
* Allows to mutate res.
|
|
50
|
-
* If you set `res.err` - it will be thrown.
|
|
51
|
-
*/
|
|
52
|
-
afterResponse?: FetcherAfterResponseHook[]
|
|
53
|
-
/**
|
|
54
|
-
* Allows to mutate res.retryStatus to override retry behavior.
|
|
55
|
-
*/
|
|
56
|
-
beforeRetry?: FetcherBeforeRetryHook[]
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* If true - enables all possible logging.
|
|
61
|
-
*/
|
|
62
|
-
debug?: boolean
|
|
63
|
-
logRequest?: boolean
|
|
64
|
-
logRequestBody?: boolean
|
|
65
|
-
logResponse?: boolean
|
|
66
|
-
logResponseBody?: boolean
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Default to true.
|
|
70
|
-
* Set to false to exclude `prefixUrl` from logs (both success and error)
|
|
71
|
-
*/
|
|
72
|
-
logWithPrefixUrl?: boolean
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Default to true.
|
|
76
|
-
* Set to false to strip searchParams from url when logging (both success and error)
|
|
77
|
-
*/
|
|
78
|
-
logWithSearchParams?: boolean
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Defaults to `console`.
|
|
82
|
-
*/
|
|
83
|
-
logger?: CommonLogger
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface FetcherRetryStatus {
|
|
87
|
-
retryAttempt: number
|
|
88
|
-
retryTimeout: number
|
|
89
|
-
retryStopped: boolean
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface FetcherRetryOptions {
|
|
93
|
-
count: number
|
|
94
|
-
timeout: number
|
|
95
|
-
timeoutMax: number
|
|
96
|
-
timeoutMultiplier: number
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
|
|
100
|
-
url: string
|
|
101
|
-
init: RequestInitNormalized
|
|
102
|
-
mode: FetcherMode
|
|
103
|
-
throwHttpErrors: boolean
|
|
104
|
-
timeoutSeconds: number
|
|
105
|
-
retry: FetcherRetryOptions
|
|
106
|
-
retryPost: boolean
|
|
107
|
-
retry4xx: boolean
|
|
108
|
-
retry5xx: boolean
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface FetcherOptions {
|
|
112
|
-
method?: HttpMethod
|
|
113
|
-
throwHttpErrors?: boolean
|
|
114
|
-
/**
|
|
115
|
-
* Default: 30.
|
|
116
|
-
*
|
|
117
|
-
* Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
|
|
118
|
-
* so both should finish within this single timeout (not each).
|
|
119
|
-
*/
|
|
120
|
-
timeoutSeconds?: number
|
|
121
|
-
|
|
122
|
-
json?: any
|
|
123
|
-
text?: string
|
|
124
|
-
/**
|
|
125
|
-
* Supports all the types that RequestInit.body supports.
|
|
126
|
-
*
|
|
127
|
-
* Useful when you want to e.g pass FormData.
|
|
128
|
-
*/
|
|
129
|
-
body?: Blob | BufferSource | FormData | URLSearchParams | string
|
|
130
|
-
|
|
131
|
-
credentials?: RequestCredentials
|
|
132
|
-
/**
|
|
133
|
-
* Default to true.
|
|
134
|
-
*/
|
|
135
|
-
followRedirects?: boolean
|
|
136
|
-
|
|
137
|
-
// Removing RequestInit from options to simplify FetcherOptions interface.
|
|
138
|
-
// Will instead only add hand-picked useful options, such as `credentials`.
|
|
139
|
-
// init?: Partial<RequestInitNormalized>
|
|
140
|
-
|
|
141
|
-
headers?: Record<string, any>
|
|
142
|
-
mode?: FetcherMode // default to 'void'
|
|
143
|
-
|
|
144
|
-
searchParams?: Record<string, any>
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Default is 2 retries (3 tries in total).
|
|
148
|
-
* Pass `retry: { count: 0 }` to disable retries.
|
|
149
|
-
*/
|
|
150
|
-
retry?: Partial<FetcherRetryOptions>
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Defaults to false.
|
|
154
|
-
* Set to true to allow retrying `post` requests.
|
|
155
|
-
*/
|
|
156
|
-
retryPost?: boolean
|
|
157
|
-
/**
|
|
158
|
-
* Defaults to false.
|
|
159
|
-
*/
|
|
160
|
-
retry4xx?: boolean
|
|
161
|
-
/**
|
|
162
|
-
* Defaults to true.
|
|
163
|
-
*/
|
|
164
|
-
retry5xx?: boolean
|
|
165
|
-
|
|
166
|
-
jsonReviver?: Reviver
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
|
|
170
|
-
method: HttpMethod
|
|
171
|
-
headers: Record<string, any>
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export interface FetcherSuccessResponse<BODY = unknown> extends FetcherResponse<BODY> {
|
|
175
|
-
err?: undefined
|
|
176
|
-
fetchResponse: Response
|
|
177
|
-
body: BODY
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export interface FetcherErrorResponse<BODY = unknown> extends FetcherResponse<BODY> {
|
|
181
|
-
err: Error
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export interface FetcherResponse<BODY = unknown> {
|
|
185
|
-
err?: Error
|
|
186
|
-
req: FetcherRequest
|
|
187
|
-
fetchResponse?: Response
|
|
188
|
-
statusFamily?: HttpStatusFamily
|
|
189
|
-
body?: BODY
|
|
190
|
-
retryStatus: FetcherRetryStatus
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export type FetcherMode = 'json' | 'text' | 'void'
|
|
29
|
+
import type { HttpStatusFamily } from './http.model'
|
|
194
30
|
|
|
195
31
|
const defRetryOptions: FetcherRetryOptions = {
|
|
196
32
|
count: 2,
|
|
@@ -300,7 +136,7 @@ export class Fetcher {
|
|
|
300
136
|
if (res.req.throwHttpErrors) throw res.err
|
|
301
137
|
return res as any
|
|
302
138
|
}
|
|
303
|
-
return res.body
|
|
139
|
+
return res.body
|
|
304
140
|
}
|
|
305
141
|
|
|
306
142
|
/**
|
|
@@ -334,14 +170,14 @@ export class Fetcher {
|
|
|
334
170
|
await hook(req)
|
|
335
171
|
}
|
|
336
172
|
|
|
337
|
-
const res
|
|
173
|
+
const res = {
|
|
338
174
|
req,
|
|
339
175
|
retryStatus: {
|
|
340
176
|
retryAttempt: 0,
|
|
341
177
|
retryStopped: false,
|
|
342
178
|
retryTimeout: req.retry.timeout,
|
|
343
179
|
},
|
|
344
|
-
}
|
|
180
|
+
} as FetcherResponse<any>
|
|
345
181
|
|
|
346
182
|
const fullUrl = new URL(req.url)
|
|
347
183
|
const shortUrl = this.getShortUrl(fullUrl)
|
|
@@ -365,9 +201,11 @@ export class Fetcher {
|
|
|
365
201
|
|
|
366
202
|
try {
|
|
367
203
|
res.fetchResponse = await globalThis.fetch(req.url, req.init)
|
|
204
|
+
res.ok = res.fetchResponse.ok
|
|
368
205
|
} catch (err) {
|
|
369
206
|
// For example, CORS error would result in "TypeError: failed to fetch" here
|
|
370
207
|
res.err = err as Error
|
|
208
|
+
res.ok = false
|
|
371
209
|
}
|
|
372
210
|
res.statusFamily = this.getStatusFamily(res)
|
|
373
211
|
|
|
@@ -380,6 +218,7 @@ export class Fetcher {
|
|
|
380
218
|
try {
|
|
381
219
|
res.body = JSON.parse(text, req.jsonReviver)
|
|
382
220
|
} catch (err) {
|
|
221
|
+
res.ok = false
|
|
383
222
|
res.err = _anyToError(
|
|
384
223
|
err,
|
|
385
224
|
HttpError,
|