@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.
@@ -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, FetcherRequest {
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?(req: FetcherRequest): Promisable<void>;
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
- beforeResponse?(res: FetcherResponse): Promisable<void>;
31
+ afterResponse?: FetcherAfterResponseHook[];
26
32
  /**
27
33
  * Allows to mutate res.retryStatus to override retry behavior.
28
34
  */
29
- beforeRetry?(res: FetcherResponse): Promisable<void>;
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
- init?: Partial<RequestInitNormalized>;
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;
@@ -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 getText(url, opt = {}) {
62
+ async putJson(url, opt) {
45
63
  return await this.fetch(url, {
46
64
  ...opt,
47
- mode: 'text',
65
+ method: 'put',
66
+ mode: 'json',
48
67
  });
49
68
  }
50
- async postText(url, opt = {}) {
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?.beforeRequest?.(req);
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?.beforeResponse?.(res);
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?.beforeRetry?.(res);
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 }, opt.init, (0, object_util_1._filterUndefinedValues)({
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
  };
@@ -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 getText(url, opt = {}) {
35
- return await this.fetch(url, Object.assign(Object.assign({}, opt), { mode: 'text' }));
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 postText(url, opt = {}) {
38
- return await this.fetch(url, Object.assign(Object.assign({}, opt), { method: 'post', mode: 'text' }));
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
- await ((_b = (_a = this.cfg.hooks) === null || _a === void 0 ? void 0 : _a.beforeRequest) === null || _b === void 0 ? void 0 : _b.call(_a, req));
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
- await ((_d = (_c = this.cfg.hooks) === null || _c === void 0 ? void 0 : _c.beforeResponse) === null || _d === void 0 ? void 0 : _d.call(_c, res));
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
- await ((_b = (_a = this.cfg.hooks) === null || _a === void 0 ? void 0 : _a.beforeRetry) === null || _b === void 0 ? void 0 : _b.call(_a, res));
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), opt.init, _filterUndefinedValues({
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.119.0",
3
+ "version": "14.120.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -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, FetcherRequest {
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?(req: FetcherRequest): Promisable<void>
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
- beforeResponse?(res: FetcherResponse): Promisable<void>
49
+ afterResponse?: FetcherAfterResponseHook[]
43
50
  /**
44
51
  * Allows to mutate res.retryStatus to override retry behavior.
45
52
  */
46
- beforeRetry?(res: FetcherResponse): Promisable<void>
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
- init?: Partial<RequestInitNormalized>
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: FetcherOptions = {}): Promise<T> {
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
- async getText(url: string, opt: FetcherOptions = {}): Promise<string> {
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
- mode: 'text',
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 postText(url: string, opt: FetcherOptions = {}): Promise<string> {
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: FetcherOptions = {}): Promise<T> {
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?.beforeRequest?.(req)
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?.beforeResponse?.(res)
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?.beforeRetry?.(res)
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