@naturalcycles/js-lib 14.119.0 → 14.120.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 +26 -6
- package/dist/http/fetcher.js +56 -31
- package/dist-esm/http/fetcher.js +105 -33
- package/package.json +1 -1
- package/src/http/fetcher.ts +81 -41
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
import { CommonLogger } from '../log/commonLogger';
|
|
3
3
|
import type { Promisable } from '../typeFest';
|
|
4
4
|
import type { HttpMethod, HttpStatusFamily } from './http.model';
|
|
5
|
-
export interface FetcherNormalizedCfg extends FetcherCfg
|
|
5
|
+
export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
|
|
6
6
|
logger: CommonLogger;
|
|
7
7
|
searchParams: Record<string, any>;
|
|
8
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>;
|
|
9
12
|
export interface FetcherCfg {
|
|
13
|
+
/**
|
|
14
|
+
* Should **not** contain trailing slash.
|
|
15
|
+
*/
|
|
10
16
|
baseUrl?: string;
|
|
11
17
|
/**
|
|
12
18
|
* Default rule is that you **are allowed** to mutate req, res, res.retryStatus
|
|
@@ -17,22 +23,28 @@ export interface FetcherCfg {
|
|
|
17
23
|
/**
|
|
18
24
|
* Allows to mutate req.
|
|
19
25
|
*/
|
|
20
|
-
beforeRequest
|
|
26
|
+
beforeRequest?: FetcherBeforeRequestHook[];
|
|
21
27
|
/**
|
|
22
28
|
* Allows to mutate res.
|
|
23
29
|
* If you set `res.err` - it will be thrown.
|
|
24
30
|
*/
|
|
25
|
-
|
|
31
|
+
afterResponse?: FetcherAfterResponseHook[];
|
|
26
32
|
/**
|
|
27
33
|
* Allows to mutate res.retryStatus to override retry behavior.
|
|
28
34
|
*/
|
|
29
|
-
beforeRetry
|
|
35
|
+
beforeRetry?: FetcherBeforeRetryHook[];
|
|
30
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* If true - enables all possible logging.
|
|
39
|
+
*/
|
|
31
40
|
debug?: boolean;
|
|
32
41
|
logRequest?: boolean;
|
|
33
42
|
logRequestBody?: boolean;
|
|
34
43
|
logResponse?: boolean;
|
|
35
44
|
logResponseBody?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Defaults to `console`.
|
|
47
|
+
*/
|
|
36
48
|
logger?: CommonLogger;
|
|
37
49
|
}
|
|
38
50
|
export interface FetcherRetryStatus {
|
|
@@ -68,7 +80,7 @@ export interface FetcherOptions {
|
|
|
68
80
|
timeoutSeconds?: number;
|
|
69
81
|
json?: any;
|
|
70
82
|
text?: string;
|
|
71
|
-
|
|
83
|
+
credentials?: RequestCredentials;
|
|
72
84
|
headers?: Record<string, any>;
|
|
73
85
|
mode?: FetcherMode;
|
|
74
86
|
searchParams?: Record<string, any>;
|
|
@@ -120,12 +132,20 @@ export type FetcherMode = 'json' | 'text';
|
|
|
120
132
|
*/
|
|
121
133
|
export declare class Fetcher {
|
|
122
134
|
private constructor();
|
|
135
|
+
/**
|
|
136
|
+
* Add BeforeRequest hook at the end of the hooks list.
|
|
137
|
+
*/
|
|
138
|
+
onBeforeRequest(hook: FetcherBeforeRequestHook): this;
|
|
139
|
+
onAfterResponse(hook: FetcherAfterResponseHook): this;
|
|
140
|
+
onBeforeRetry(hook: FetcherBeforeRetryHook): this;
|
|
123
141
|
cfg: FetcherNormalizedCfg;
|
|
124
142
|
static create(cfg?: FetcherCfg & FetcherOptions): Fetcher;
|
|
125
143
|
getJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
|
|
126
144
|
postJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
|
|
145
|
+
putJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
|
|
146
|
+
patchJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
|
|
147
|
+
deleteJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
|
|
127
148
|
getText(url: string, opt?: FetcherOptions): Promise<string>;
|
|
128
|
-
postText(url: string, opt?: FetcherOptions): Promise<string>;
|
|
129
149
|
fetch<T = unknown>(url: string, opt?: FetcherOptions): Promise<T>;
|
|
130
150
|
rawFetch<T = unknown>(url: string, rawOpt?: FetcherOptions): Promise<FetcherResponse<T>>;
|
|
131
151
|
private processRetry;
|
package/dist/http/fetcher.js
CHANGED
|
@@ -25,36 +25,68 @@ class Fetcher {
|
|
|
25
25
|
constructor(cfg = {}) {
|
|
26
26
|
this.cfg = this.normalizeCfg(cfg);
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Add BeforeRequest hook at the end of the hooks list.
|
|
30
|
+
*/
|
|
31
|
+
onBeforeRequest(hook) {
|
|
32
|
+
;
|
|
33
|
+
(this.cfg.hooks.beforeRequest ||= []).push(hook);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
onAfterResponse(hook) {
|
|
37
|
+
;
|
|
38
|
+
(this.cfg.hooks.afterResponse ||= []).push(hook);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
onBeforeRetry(hook) {
|
|
42
|
+
;
|
|
43
|
+
(this.cfg.hooks.beforeRetry ||= []).push(hook);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
28
46
|
static create(cfg = {}) {
|
|
29
47
|
return new Fetcher(cfg);
|
|
30
48
|
}
|
|
31
|
-
async getJson(url, opt
|
|
49
|
+
async getJson(url, opt) {
|
|
32
50
|
return await this.fetch(url, {
|
|
33
51
|
...opt,
|
|
34
52
|
mode: 'json',
|
|
35
53
|
});
|
|
36
54
|
}
|
|
37
|
-
async postJson(url, opt
|
|
55
|
+
async postJson(url, opt) {
|
|
38
56
|
return await this.fetch(url, {
|
|
39
57
|
...opt,
|
|
40
58
|
method: 'post',
|
|
41
59
|
mode: 'json',
|
|
42
60
|
});
|
|
43
61
|
}
|
|
44
|
-
async
|
|
62
|
+
async putJson(url, opt) {
|
|
45
63
|
return await this.fetch(url, {
|
|
46
64
|
...opt,
|
|
47
|
-
|
|
65
|
+
method: 'put',
|
|
66
|
+
mode: 'json',
|
|
48
67
|
});
|
|
49
68
|
}
|
|
50
|
-
async
|
|
69
|
+
async patchJson(url, opt) {
|
|
70
|
+
return await this.fetch(url, {
|
|
71
|
+
...opt,
|
|
72
|
+
method: 'patch',
|
|
73
|
+
mode: 'json',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async deleteJson(url, opt) {
|
|
77
|
+
return await this.fetch(url, {
|
|
78
|
+
...opt,
|
|
79
|
+
method: 'delete',
|
|
80
|
+
mode: 'json',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async getText(url, opt) {
|
|
51
84
|
return await this.fetch(url, {
|
|
52
85
|
...opt,
|
|
53
|
-
method: 'post',
|
|
54
86
|
mode: 'text',
|
|
55
87
|
});
|
|
56
88
|
}
|
|
57
|
-
async fetch(url, opt
|
|
89
|
+
async fetch(url, opt) {
|
|
58
90
|
const res = await this.rawFetch(url, opt);
|
|
59
91
|
if (res.err) {
|
|
60
92
|
if (res.req.throwHttpErrors)
|
|
@@ -76,7 +108,9 @@ class Fetcher {
|
|
|
76
108
|
abortController.abort(`timeout of ${timeoutSeconds} sec`);
|
|
77
109
|
}, timeoutSeconds * 1000);
|
|
78
110
|
}
|
|
79
|
-
await this.cfg.hooks
|
|
111
|
+
for await (const hook of this.cfg.hooks.beforeRequest || []) {
|
|
112
|
+
await hook(req);
|
|
113
|
+
}
|
|
80
114
|
const res = {
|
|
81
115
|
req,
|
|
82
116
|
retryStatus: {
|
|
@@ -145,29 +179,12 @@ class Fetcher {
|
|
|
145
179
|
url: req.url,
|
|
146
180
|
// tryCount: req.tryCount,
|
|
147
181
|
}));
|
|
148
|
-
// We don't log errors when they are also thrown,
|
|
149
|
-
// otherwise it gets logged twice: here, and upstream
|
|
150
|
-
// if (this.cfg.logResponse) {
|
|
151
|
-
// const { retryAttempt } = res.retryStatus
|
|
152
|
-
// logger.error(
|
|
153
|
-
// [
|
|
154
|
-
// [
|
|
155
|
-
// ' <<',
|
|
156
|
-
// res.fetchResponse.status,
|
|
157
|
-
// signature,
|
|
158
|
-
// retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
|
|
159
|
-
// _since(started),
|
|
160
|
-
// ]
|
|
161
|
-
// .filter(Boolean)
|
|
162
|
-
// .join(' '),
|
|
163
|
-
// _stringifyAny(body),
|
|
164
|
-
// ].join('\n'),
|
|
165
|
-
// )
|
|
166
|
-
// }
|
|
167
182
|
await this.processRetry(res);
|
|
168
183
|
}
|
|
169
184
|
}
|
|
170
|
-
await this.cfg.hooks
|
|
185
|
+
for await (const hook of this.cfg.hooks.afterResponse || []) {
|
|
186
|
+
await hook(res);
|
|
187
|
+
}
|
|
171
188
|
return res;
|
|
172
189
|
}
|
|
173
190
|
async processRetry(res) {
|
|
@@ -175,7 +192,9 @@ class Fetcher {
|
|
|
175
192
|
if (!this.shouldRetry(res)) {
|
|
176
193
|
retryStatus.retryStopped = true;
|
|
177
194
|
}
|
|
178
|
-
await this.cfg.hooks
|
|
195
|
+
for await (const hook of this.cfg.hooks.beforeRetry || []) {
|
|
196
|
+
await hook(res);
|
|
197
|
+
}
|
|
179
198
|
const { count, timeoutMultiplier, timeoutMax } = res.req.retry;
|
|
180
199
|
if (retryStatus.retryAttempt >= count) {
|
|
181
200
|
retryStatus.retryStopped = true;
|
|
@@ -231,8 +250,9 @@ class Fetcher {
|
|
|
231
250
|
console.warn(`Fetcher: baseUrl should not end with /`);
|
|
232
251
|
cfg.baseUrl = cfg.baseUrl.slice(0, cfg.baseUrl.length - 1);
|
|
233
252
|
}
|
|
234
|
-
const { debug } = cfg;
|
|
253
|
+
const { debug = false } = cfg;
|
|
235
254
|
const norm = (0, object_util_1._merge)({
|
|
255
|
+
baseUrl: '',
|
|
236
256
|
url: '',
|
|
237
257
|
searchParams: {},
|
|
238
258
|
timeoutSeconds: 30,
|
|
@@ -241,6 +261,7 @@ class Fetcher {
|
|
|
241
261
|
retry4xx: false,
|
|
242
262
|
retry5xx: true,
|
|
243
263
|
logger: console,
|
|
264
|
+
debug,
|
|
244
265
|
logRequest: debug,
|
|
245
266
|
logRequestBody: debug,
|
|
246
267
|
logResponse: debug,
|
|
@@ -250,6 +271,7 @@ class Fetcher {
|
|
|
250
271
|
method: 'get',
|
|
251
272
|
headers: {},
|
|
252
273
|
},
|
|
274
|
+
hooks: {},
|
|
253
275
|
}, cfg);
|
|
254
276
|
norm.init.headers = (0, object_util_1._mapKeys)(norm.init.headers, k => k.toLowerCase());
|
|
255
277
|
return norm;
|
|
@@ -268,8 +290,11 @@ class Fetcher {
|
|
|
268
290
|
...retry,
|
|
269
291
|
...(0, object_util_1._filterUndefinedValues)(opt.retry || {}),
|
|
270
292
|
},
|
|
271
|
-
init: (0, object_util_1._merge)({ ...this.cfg.init },
|
|
293
|
+
init: (0, object_util_1._merge)({ ...this.cfg.init },
|
|
294
|
+
// opt.init,
|
|
295
|
+
(0, object_util_1._filterUndefinedValues)({
|
|
272
296
|
method: opt.method,
|
|
297
|
+
credentials: opt.credentials,
|
|
273
298
|
headers: (0, object_util_1._mapKeys)(opt.headers || {}, k => k.toLowerCase()),
|
|
274
299
|
})),
|
|
275
300
|
};
|
package/dist-esm/http/fetcher.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference lib="dom"/>
|
|
2
|
+
import { __asyncValues } from "tslib";
|
|
2
3
|
import { _anyToErrorObject } from '../error/error.util';
|
|
3
4
|
import { HttpError } from '../error/http.error';
|
|
4
5
|
import { _clamp } from '../number/number.util';
|
|
@@ -22,22 +23,49 @@ export class Fetcher {
|
|
|
22
23
|
constructor(cfg = {}) {
|
|
23
24
|
this.cfg = this.normalizeCfg(cfg);
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Add BeforeRequest hook at the end of the hooks list.
|
|
28
|
+
*/
|
|
29
|
+
onBeforeRequest(hook) {
|
|
30
|
+
var _a;
|
|
31
|
+
;
|
|
32
|
+
((_a = this.cfg.hooks).beforeRequest || (_a.beforeRequest = [])).push(hook);
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
onAfterResponse(hook) {
|
|
36
|
+
var _a;
|
|
37
|
+
;
|
|
38
|
+
((_a = this.cfg.hooks).afterResponse || (_a.afterResponse = [])).push(hook);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
onBeforeRetry(hook) {
|
|
42
|
+
var _a;
|
|
43
|
+
;
|
|
44
|
+
((_a = this.cfg.hooks).beforeRetry || (_a.beforeRetry = [])).push(hook);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
25
47
|
static create(cfg = {}) {
|
|
26
48
|
return new Fetcher(cfg);
|
|
27
49
|
}
|
|
28
|
-
async getJson(url, opt
|
|
50
|
+
async getJson(url, opt) {
|
|
29
51
|
return await this.fetch(url, Object.assign(Object.assign({}, opt), { mode: 'json' }));
|
|
30
52
|
}
|
|
31
|
-
async postJson(url, opt
|
|
53
|
+
async postJson(url, opt) {
|
|
32
54
|
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method: 'post', mode: 'json' }));
|
|
33
55
|
}
|
|
34
|
-
async
|
|
35
|
-
return await this.fetch(url, Object.assign(Object.assign({}, opt), { mode: '
|
|
56
|
+
async putJson(url, opt) {
|
|
57
|
+
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method: 'put', mode: 'json' }));
|
|
58
|
+
}
|
|
59
|
+
async patchJson(url, opt) {
|
|
60
|
+
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method: 'patch', mode: 'json' }));
|
|
36
61
|
}
|
|
37
|
-
async
|
|
38
|
-
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method: '
|
|
62
|
+
async deleteJson(url, opt) {
|
|
63
|
+
return await this.fetch(url, Object.assign(Object.assign({}, opt), { method: 'delete', mode: 'json' }));
|
|
64
|
+
}
|
|
65
|
+
async getText(url, opt) {
|
|
66
|
+
return await this.fetch(url, Object.assign(Object.assign({}, opt), { mode: 'text' }));
|
|
39
67
|
}
|
|
40
|
-
async fetch(url, opt
|
|
68
|
+
async fetch(url, opt) {
|
|
41
69
|
const res = await this.rawFetch(url, opt);
|
|
42
70
|
if (res.err) {
|
|
43
71
|
if (res.req.throwHttpErrors)
|
|
@@ -47,7 +75,7 @@ export class Fetcher {
|
|
|
47
75
|
return res.body;
|
|
48
76
|
}
|
|
49
77
|
async rawFetch(url, rawOpt = {}) {
|
|
50
|
-
var _a, _b, _c, _d;
|
|
78
|
+
var _a, e_1, _b, _c, _d, e_2, _e, _f;
|
|
51
79
|
const { logger } = this.cfg;
|
|
52
80
|
const req = this.normalizeOptions(url, rawOpt);
|
|
53
81
|
const { timeoutSeconds, mode, init: { method }, } = req;
|
|
@@ -60,7 +88,26 @@ export class Fetcher {
|
|
|
60
88
|
abortController.abort(`timeout of ${timeoutSeconds} sec`);
|
|
61
89
|
}, timeoutSeconds * 1000);
|
|
62
90
|
}
|
|
63
|
-
|
|
91
|
+
try {
|
|
92
|
+
for (var _g = true, _h = __asyncValues(this.cfg.hooks.beforeRequest || []), _j; _j = await _h.next(), _a = _j.done, !_a;) {
|
|
93
|
+
_c = _j.value;
|
|
94
|
+
_g = false;
|
|
95
|
+
try {
|
|
96
|
+
const hook = _c;
|
|
97
|
+
await hook(req);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
_g = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
105
|
+
finally {
|
|
106
|
+
try {
|
|
107
|
+
if (!_g && !_a && (_b = _h.return)) await _b.call(_h);
|
|
108
|
+
}
|
|
109
|
+
finally { if (e_1) throw e_1.error; }
|
|
110
|
+
}
|
|
64
111
|
const res = {
|
|
65
112
|
req,
|
|
66
113
|
retryStatus: {
|
|
@@ -124,38 +171,57 @@ export class Fetcher {
|
|
|
124
171
|
// Enabled, cause `data` is not printed by default when error is HttpError
|
|
125
172
|
// method: req.method,
|
|
126
173
|
url: req.url })));
|
|
127
|
-
// We don't log errors when they are also thrown,
|
|
128
|
-
// otherwise it gets logged twice: here, and upstream
|
|
129
|
-
// if (this.cfg.logResponse) {
|
|
130
|
-
// const { retryAttempt } = res.retryStatus
|
|
131
|
-
// logger.error(
|
|
132
|
-
// [
|
|
133
|
-
// [
|
|
134
|
-
// ' <<',
|
|
135
|
-
// res.fetchResponse.status,
|
|
136
|
-
// signature,
|
|
137
|
-
// retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
|
|
138
|
-
// _since(started),
|
|
139
|
-
// ]
|
|
140
|
-
// .filter(Boolean)
|
|
141
|
-
// .join(' '),
|
|
142
|
-
// _stringifyAny(body),
|
|
143
|
-
// ].join('\n'),
|
|
144
|
-
// )
|
|
145
|
-
// }
|
|
146
174
|
await this.processRetry(res);
|
|
147
175
|
}
|
|
148
176
|
}
|
|
149
|
-
|
|
177
|
+
try {
|
|
178
|
+
for (var _k = true, _l = __asyncValues(this.cfg.hooks.afterResponse || []), _m; _m = await _l.next(), _d = _m.done, !_d;) {
|
|
179
|
+
_f = _m.value;
|
|
180
|
+
_k = false;
|
|
181
|
+
try {
|
|
182
|
+
const hook = _f;
|
|
183
|
+
await hook(res);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
_k = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
191
|
+
finally {
|
|
192
|
+
try {
|
|
193
|
+
if (!_k && !_d && (_e = _l.return)) await _e.call(_l);
|
|
194
|
+
}
|
|
195
|
+
finally { if (e_2) throw e_2.error; }
|
|
196
|
+
}
|
|
150
197
|
return res;
|
|
151
198
|
}
|
|
152
199
|
async processRetry(res) {
|
|
153
|
-
var _a, _b;
|
|
200
|
+
var _a, e_3, _b, _c;
|
|
154
201
|
const { retryStatus } = res;
|
|
155
202
|
if (!this.shouldRetry(res)) {
|
|
156
203
|
retryStatus.retryStopped = true;
|
|
157
204
|
}
|
|
158
|
-
|
|
205
|
+
try {
|
|
206
|
+
for (var _d = true, _e = __asyncValues(this.cfg.hooks.beforeRetry || []), _f; _f = await _e.next(), _a = _f.done, !_a;) {
|
|
207
|
+
_c = _f.value;
|
|
208
|
+
_d = false;
|
|
209
|
+
try {
|
|
210
|
+
const hook = _c;
|
|
211
|
+
await hook(res);
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
_d = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
219
|
+
finally {
|
|
220
|
+
try {
|
|
221
|
+
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
|
|
222
|
+
}
|
|
223
|
+
finally { if (e_3) throw e_3.error; }
|
|
224
|
+
}
|
|
159
225
|
const { count, timeoutMultiplier, timeoutMax } = res.req.retry;
|
|
160
226
|
if (retryStatus.retryAttempt >= count) {
|
|
161
227
|
retryStatus.retryStopped = true;
|
|
@@ -213,8 +279,9 @@ export class Fetcher {
|
|
|
213
279
|
console.warn(`Fetcher: baseUrl should not end with /`);
|
|
214
280
|
cfg.baseUrl = cfg.baseUrl.slice(0, cfg.baseUrl.length - 1);
|
|
215
281
|
}
|
|
216
|
-
const { debug } = cfg;
|
|
282
|
+
const { debug = false } = cfg;
|
|
217
283
|
const norm = _merge({
|
|
284
|
+
baseUrl: '',
|
|
218
285
|
url: '',
|
|
219
286
|
searchParams: {},
|
|
220
287
|
timeoutSeconds: 30,
|
|
@@ -223,6 +290,7 @@ export class Fetcher {
|
|
|
223
290
|
retry4xx: false,
|
|
224
291
|
retry5xx: true,
|
|
225
292
|
logger: console,
|
|
293
|
+
debug,
|
|
226
294
|
logRequest: debug,
|
|
227
295
|
logRequestBody: debug,
|
|
228
296
|
logResponse: debug,
|
|
@@ -232,6 +300,7 @@ export class Fetcher {
|
|
|
232
300
|
method: 'get',
|
|
233
301
|
headers: {},
|
|
234
302
|
},
|
|
303
|
+
hooks: {},
|
|
235
304
|
}, cfg);
|
|
236
305
|
norm.init.headers = _mapKeys(norm.init.headers, k => k.toLowerCase());
|
|
237
306
|
return norm;
|
|
@@ -243,8 +312,11 @@ export class Fetcher {
|
|
|
243
312
|
throwHttpErrors,
|
|
244
313
|
retryPost,
|
|
245
314
|
retry4xx,
|
|
246
|
-
retry5xx }, _omit(opt, ['method', 'headers'])), { retry: Object.assign(Object.assign({}, retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign({}, this.cfg.init),
|
|
315
|
+
retry5xx }, _omit(opt, ['method', 'headers'])), { retry: Object.assign(Object.assign({}, retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign({}, this.cfg.init),
|
|
316
|
+
// opt.init,
|
|
317
|
+
_filterUndefinedValues({
|
|
247
318
|
method: opt.method,
|
|
319
|
+
credentials: opt.credentials,
|
|
248
320
|
headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
|
|
249
321
|
})) });
|
|
250
322
|
// setup url
|
package/package.json
CHANGED
package/src/http/fetcher.ts
CHANGED
|
@@ -17,12 +17,19 @@ import { _since } from '../time/time.util'
|
|
|
17
17
|
import type { Promisable } from '../typeFest'
|
|
18
18
|
import type { HttpMethod, HttpStatusFamily } from './http.model'
|
|
19
19
|
|
|
20
|
-
export interface FetcherNormalizedCfg extends FetcherCfg
|
|
20
|
+
export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
|
|
21
21
|
logger: CommonLogger
|
|
22
22
|
searchParams: Record<string, any>
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>
|
|
26
|
+
export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>
|
|
27
|
+
export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>
|
|
28
|
+
|
|
25
29
|
export interface FetcherCfg {
|
|
30
|
+
/**
|
|
31
|
+
* Should **not** contain trailing slash.
|
|
32
|
+
*/
|
|
26
33
|
baseUrl?: string
|
|
27
34
|
|
|
28
35
|
/**
|
|
@@ -34,23 +41,30 @@ export interface FetcherCfg {
|
|
|
34
41
|
/**
|
|
35
42
|
* Allows to mutate req.
|
|
36
43
|
*/
|
|
37
|
-
beforeRequest
|
|
44
|
+
beforeRequest?: FetcherBeforeRequestHook[]
|
|
38
45
|
/**
|
|
39
46
|
* Allows to mutate res.
|
|
40
47
|
* If you set `res.err` - it will be thrown.
|
|
41
48
|
*/
|
|
42
|
-
|
|
49
|
+
afterResponse?: FetcherAfterResponseHook[]
|
|
43
50
|
/**
|
|
44
51
|
* Allows to mutate res.retryStatus to override retry behavior.
|
|
45
52
|
*/
|
|
46
|
-
beforeRetry
|
|
53
|
+
beforeRetry?: FetcherBeforeRetryHook[]
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
/**
|
|
57
|
+
* If true - enables all possible logging.
|
|
58
|
+
*/
|
|
49
59
|
debug?: boolean
|
|
50
60
|
logRequest?: boolean
|
|
51
61
|
logRequestBody?: boolean
|
|
52
62
|
logResponse?: boolean
|
|
53
63
|
logResponseBody?: boolean
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Defaults to `console`.
|
|
67
|
+
*/
|
|
54
68
|
logger?: CommonLogger
|
|
55
69
|
}
|
|
56
70
|
|
|
@@ -90,7 +104,13 @@ export interface FetcherOptions {
|
|
|
90
104
|
timeoutSeconds?: number
|
|
91
105
|
json?: any
|
|
92
106
|
text?: string
|
|
93
|
-
|
|
107
|
+
|
|
108
|
+
credentials?: RequestCredentials
|
|
109
|
+
|
|
110
|
+
// Removing RequestInit from options to simplify FetcherOptions interface.
|
|
111
|
+
// Will instead only add hand-picked useful options, such as `credentials`.
|
|
112
|
+
// init?: Partial<RequestInitNormalized>
|
|
113
|
+
|
|
94
114
|
headers?: Record<string, any>
|
|
95
115
|
mode?: FetcherMode // default to undefined (void response)
|
|
96
116
|
|
|
@@ -161,43 +181,73 @@ export class Fetcher {
|
|
|
161
181
|
this.cfg = this.normalizeCfg(cfg)
|
|
162
182
|
}
|
|
163
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Add BeforeRequest hook at the end of the hooks list.
|
|
186
|
+
*/
|
|
187
|
+
onBeforeRequest(hook: FetcherBeforeRequestHook): this {
|
|
188
|
+
;(this.cfg.hooks.beforeRequest ||= []).push(hook)
|
|
189
|
+
return this
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
onAfterResponse(hook: FetcherAfterResponseHook): this {
|
|
193
|
+
;(this.cfg.hooks.afterResponse ||= []).push(hook)
|
|
194
|
+
return this
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
onBeforeRetry(hook: FetcherBeforeRetryHook): this {
|
|
198
|
+
;(this.cfg.hooks.beforeRetry ||= []).push(hook)
|
|
199
|
+
return this
|
|
200
|
+
}
|
|
201
|
+
|
|
164
202
|
public cfg: FetcherNormalizedCfg
|
|
165
203
|
|
|
166
204
|
static create(cfg: FetcherCfg & FetcherOptions = {}): Fetcher {
|
|
167
205
|
return new Fetcher(cfg)
|
|
168
206
|
}
|
|
169
207
|
|
|
170
|
-
async getJson<T = unknown>(url: string, opt
|
|
208
|
+
async getJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
|
|
171
209
|
return await this.fetch<T>(url, {
|
|
172
210
|
...opt,
|
|
173
211
|
mode: 'json',
|
|
174
212
|
})
|
|
175
213
|
}
|
|
176
|
-
|
|
177
|
-
async postJson<T = unknown>(url: string, opt: FetcherOptions = {}): Promise<T> {
|
|
214
|
+
async postJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
|
|
178
215
|
return await this.fetch<T>(url, {
|
|
179
216
|
...opt,
|
|
180
217
|
method: 'post',
|
|
181
218
|
mode: 'json',
|
|
182
219
|
})
|
|
183
220
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return await this.fetch<string>(url, {
|
|
221
|
+
async putJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
|
|
222
|
+
return await this.fetch<T>(url, {
|
|
187
223
|
...opt,
|
|
188
|
-
|
|
224
|
+
method: 'put',
|
|
225
|
+
mode: 'json',
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
async patchJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
|
|
229
|
+
return await this.fetch<T>(url, {
|
|
230
|
+
...opt,
|
|
231
|
+
method: 'patch',
|
|
232
|
+
mode: 'json',
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
async deleteJson<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
|
|
236
|
+
return await this.fetch<T>(url, {
|
|
237
|
+
...opt,
|
|
238
|
+
method: 'delete',
|
|
239
|
+
mode: 'json',
|
|
189
240
|
})
|
|
190
241
|
}
|
|
191
242
|
|
|
192
|
-
async
|
|
243
|
+
async getText(url: string, opt?: FetcherOptions): Promise<string> {
|
|
193
244
|
return await this.fetch<string>(url, {
|
|
194
245
|
...opt,
|
|
195
|
-
method: 'post',
|
|
196
246
|
mode: 'text',
|
|
197
247
|
})
|
|
198
248
|
}
|
|
199
249
|
|
|
200
|
-
async fetch<T = unknown>(url: string, opt
|
|
250
|
+
async fetch<T = unknown>(url: string, opt?: FetcherOptions): Promise<T> {
|
|
201
251
|
const res = await this.rawFetch<T>(url, opt)
|
|
202
252
|
if (res.err) {
|
|
203
253
|
if (res.req.throwHttpErrors) throw res.err
|
|
@@ -229,7 +279,9 @@ export class Fetcher {
|
|
|
229
279
|
}, timeoutSeconds * 1000) as any as number
|
|
230
280
|
}
|
|
231
281
|
|
|
232
|
-
await this.cfg.hooks
|
|
282
|
+
for await (const hook of this.cfg.hooks.beforeRequest || []) {
|
|
283
|
+
await hook(req)
|
|
284
|
+
}
|
|
233
285
|
|
|
234
286
|
const res: FetcherResponse<any> = {
|
|
235
287
|
req,
|
|
@@ -318,31 +370,13 @@ export class Fetcher {
|
|
|
318
370
|
}),
|
|
319
371
|
)
|
|
320
372
|
|
|
321
|
-
// We don't log errors when they are also thrown,
|
|
322
|
-
// otherwise it gets logged twice: here, and upstream
|
|
323
|
-
// if (this.cfg.logResponse) {
|
|
324
|
-
// const { retryAttempt } = res.retryStatus
|
|
325
|
-
// logger.error(
|
|
326
|
-
// [
|
|
327
|
-
// [
|
|
328
|
-
// ' <<',
|
|
329
|
-
// res.fetchResponse.status,
|
|
330
|
-
// signature,
|
|
331
|
-
// retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
|
|
332
|
-
// _since(started),
|
|
333
|
-
// ]
|
|
334
|
-
// .filter(Boolean)
|
|
335
|
-
// .join(' '),
|
|
336
|
-
// _stringifyAny(body),
|
|
337
|
-
// ].join('\n'),
|
|
338
|
-
// )
|
|
339
|
-
// }
|
|
340
|
-
|
|
341
373
|
await this.processRetry(res)
|
|
342
374
|
}
|
|
343
375
|
}
|
|
344
376
|
|
|
345
|
-
await this.cfg.hooks
|
|
377
|
+
for await (const hook of this.cfg.hooks.afterResponse || []) {
|
|
378
|
+
await hook(res)
|
|
379
|
+
}
|
|
346
380
|
|
|
347
381
|
return res
|
|
348
382
|
}
|
|
@@ -354,7 +388,9 @@ export class Fetcher {
|
|
|
354
388
|
retryStatus.retryStopped = true
|
|
355
389
|
}
|
|
356
390
|
|
|
357
|
-
await this.cfg.hooks
|
|
391
|
+
for await (const hook of this.cfg.hooks.beforeRetry || []) {
|
|
392
|
+
await hook(res)
|
|
393
|
+
}
|
|
358
394
|
|
|
359
395
|
const { count, timeoutMultiplier, timeoutMax } = res.req.retry
|
|
360
396
|
|
|
@@ -409,10 +445,11 @@ export class Fetcher {
|
|
|
409
445
|
console.warn(`Fetcher: baseUrl should not end with /`)
|
|
410
446
|
cfg.baseUrl = cfg.baseUrl.slice(0, cfg.baseUrl.length - 1)
|
|
411
447
|
}
|
|
412
|
-
const { debug } = cfg
|
|
448
|
+
const { debug = false } = cfg
|
|
413
449
|
|
|
414
450
|
const norm: FetcherNormalizedCfg = _merge(
|
|
415
451
|
{
|
|
452
|
+
baseUrl: '',
|
|
416
453
|
url: '',
|
|
417
454
|
searchParams: {},
|
|
418
455
|
timeoutSeconds: 30,
|
|
@@ -421,6 +458,7 @@ export class Fetcher {
|
|
|
421
458
|
retry4xx: false,
|
|
422
459
|
retry5xx: true,
|
|
423
460
|
logger: console,
|
|
461
|
+
debug,
|
|
424
462
|
logRequest: debug,
|
|
425
463
|
logRequestBody: debug,
|
|
426
464
|
logResponse: debug,
|
|
@@ -430,6 +468,7 @@ export class Fetcher {
|
|
|
430
468
|
method: 'get',
|
|
431
469
|
headers: {},
|
|
432
470
|
},
|
|
471
|
+
hooks: {},
|
|
433
472
|
},
|
|
434
473
|
cfg,
|
|
435
474
|
)
|
|
@@ -457,11 +496,12 @@ export class Fetcher {
|
|
|
457
496
|
},
|
|
458
497
|
init: _merge(
|
|
459
498
|
{ ...this.cfg.init },
|
|
460
|
-
opt.init,
|
|
499
|
+
// opt.init,
|
|
461
500
|
_filterUndefinedValues({
|
|
462
501
|
method: opt.method,
|
|
502
|
+
credentials: opt.credentials,
|
|
463
503
|
headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
|
|
464
|
-
}),
|
|
504
|
+
} as RequestInit),
|
|
465
505
|
),
|
|
466
506
|
}
|
|
467
507
|
|