@naturalcycles/js-lib 14.119.1 → 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,6 +132,12 @@ 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>;
@@ -25,6 +25,24 @@ 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
  }
@@ -90,7 +108,9 @@ class Fetcher {
90
108
  abortController.abort(`timeout of ${timeoutSeconds} sec`);
91
109
  }, timeoutSeconds * 1000);
92
110
  }
93
- await this.cfg.hooks?.beforeRequest?.(req);
111
+ for await (const hook of this.cfg.hooks.beforeRequest || []) {
112
+ await hook(req);
113
+ }
94
114
  const res = {
95
115
  req,
96
116
  retryStatus: {
@@ -159,29 +179,12 @@ class Fetcher {
159
179
  url: req.url,
160
180
  // tryCount: req.tryCount,
161
181
  }));
162
- // We don't log errors when they are also thrown,
163
- // otherwise it gets logged twice: here, and upstream
164
- // if (this.cfg.logResponse) {
165
- // const { retryAttempt } = res.retryStatus
166
- // logger.error(
167
- // [
168
- // [
169
- // ' <<',
170
- // res.fetchResponse.status,
171
- // signature,
172
- // retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
173
- // _since(started),
174
- // ]
175
- // .filter(Boolean)
176
- // .join(' '),
177
- // _stringifyAny(body),
178
- // ].join('\n'),
179
- // )
180
- // }
181
182
  await this.processRetry(res);
182
183
  }
183
184
  }
184
- await this.cfg.hooks?.beforeResponse?.(res);
185
+ for await (const hook of this.cfg.hooks.afterResponse || []) {
186
+ await hook(res);
187
+ }
185
188
  return res;
186
189
  }
187
190
  async processRetry(res) {
@@ -189,7 +192,9 @@ class Fetcher {
189
192
  if (!this.shouldRetry(res)) {
190
193
  retryStatus.retryStopped = true;
191
194
  }
192
- await this.cfg.hooks?.beforeRetry?.(res);
195
+ for await (const hook of this.cfg.hooks.beforeRetry || []) {
196
+ await hook(res);
197
+ }
193
198
  const { count, timeoutMultiplier, timeoutMax } = res.req.retry;
194
199
  if (retryStatus.retryAttempt >= count) {
195
200
  retryStatus.retryStopped = true;
@@ -245,8 +250,9 @@ class Fetcher {
245
250
  console.warn(`Fetcher: baseUrl should not end with /`);
246
251
  cfg.baseUrl = cfg.baseUrl.slice(0, cfg.baseUrl.length - 1);
247
252
  }
248
- const { debug } = cfg;
253
+ const { debug = false } = cfg;
249
254
  const norm = (0, object_util_1._merge)({
255
+ baseUrl: '',
250
256
  url: '',
251
257
  searchParams: {},
252
258
  timeoutSeconds: 30,
@@ -255,6 +261,7 @@ class Fetcher {
255
261
  retry4xx: false,
256
262
  retry5xx: true,
257
263
  logger: console,
264
+ debug,
258
265
  logRequest: debug,
259
266
  logRequestBody: debug,
260
267
  logResponse: debug,
@@ -264,6 +271,7 @@ class Fetcher {
264
271
  method: 'get',
265
272
  headers: {},
266
273
  },
274
+ hooks: {},
267
275
  }, cfg);
268
276
  norm.init.headers = (0, object_util_1._mapKeys)(norm.init.headers, k => k.toLowerCase());
269
277
  return norm;
@@ -282,8 +290,11 @@ class Fetcher {
282
290
  ...retry,
283
291
  ...(0, object_util_1._filterUndefinedValues)(opt.retry || {}),
284
292
  },
285
- 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)({
286
296
  method: opt.method,
297
+ credentials: opt.credentials,
287
298
  headers: (0, object_util_1._mapKeys)(opt.headers || {}, k => k.toLowerCase()),
288
299
  })),
289
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,6 +23,27 @@ 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
  }
@@ -53,7 +75,7 @@ export class Fetcher {
53
75
  return res.body;
54
76
  }
55
77
  async rawFetch(url, rawOpt = {}) {
56
- var _a, _b, _c, _d;
78
+ var _a, e_1, _b, _c, _d, e_2, _e, _f;
57
79
  const { logger } = this.cfg;
58
80
  const req = this.normalizeOptions(url, rawOpt);
59
81
  const { timeoutSeconds, mode, init: { method }, } = req;
@@ -66,7 +88,26 @@ export class Fetcher {
66
88
  abortController.abort(`timeout of ${timeoutSeconds} sec`);
67
89
  }, timeoutSeconds * 1000);
68
90
  }
69
- 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
+ }
70
111
  const res = {
71
112
  req,
72
113
  retryStatus: {
@@ -130,38 +171,57 @@ export class Fetcher {
130
171
  // Enabled, cause `data` is not printed by default when error is HttpError
131
172
  // method: req.method,
132
173
  url: req.url })));
133
- // We don't log errors when they are also thrown,
134
- // otherwise it gets logged twice: here, and upstream
135
- // if (this.cfg.logResponse) {
136
- // const { retryAttempt } = res.retryStatus
137
- // logger.error(
138
- // [
139
- // [
140
- // ' <<',
141
- // res.fetchResponse.status,
142
- // signature,
143
- // retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
144
- // _since(started),
145
- // ]
146
- // .filter(Boolean)
147
- // .join(' '),
148
- // _stringifyAny(body),
149
- // ].join('\n'),
150
- // )
151
- // }
152
174
  await this.processRetry(res);
153
175
  }
154
176
  }
155
- 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
+ }
156
197
  return res;
157
198
  }
158
199
  async processRetry(res) {
159
- var _a, _b;
200
+ var _a, e_3, _b, _c;
160
201
  const { retryStatus } = res;
161
202
  if (!this.shouldRetry(res)) {
162
203
  retryStatus.retryStopped = true;
163
204
  }
164
- 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
+ }
165
225
  const { count, timeoutMultiplier, timeoutMax } = res.req.retry;
166
226
  if (retryStatus.retryAttempt >= count) {
167
227
  retryStatus.retryStopped = true;
@@ -219,8 +279,9 @@ export class Fetcher {
219
279
  console.warn(`Fetcher: baseUrl should not end with /`);
220
280
  cfg.baseUrl = cfg.baseUrl.slice(0, cfg.baseUrl.length - 1);
221
281
  }
222
- const { debug } = cfg;
282
+ const { debug = false } = cfg;
223
283
  const norm = _merge({
284
+ baseUrl: '',
224
285
  url: '',
225
286
  searchParams: {},
226
287
  timeoutSeconds: 30,
@@ -229,6 +290,7 @@ export class Fetcher {
229
290
  retry4xx: false,
230
291
  retry5xx: true,
231
292
  logger: console,
293
+ debug,
232
294
  logRequest: debug,
233
295
  logRequestBody: debug,
234
296
  logResponse: debug,
@@ -238,6 +300,7 @@ export class Fetcher {
238
300
  method: 'get',
239
301
  headers: {},
240
302
  },
303
+ hooks: {},
241
304
  }, cfg);
242
305
  norm.init.headers = _mapKeys(norm.init.headers, k => k.toLowerCase());
243
306
  return norm;
@@ -249,8 +312,11 @@ export class Fetcher {
249
312
  throwHttpErrors,
250
313
  retryPost,
251
314
  retry4xx,
252
- 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({
253
318
  method: opt.method,
319
+ credentials: opt.credentials,
254
320
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
255
321
  })) });
256
322
  // setup url
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.119.1",
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,6 +181,24 @@ 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 {
@@ -241,7 +279,9 @@ export class Fetcher {
241
279
  }, timeoutSeconds * 1000) as any as number
242
280
  }
243
281
 
244
- await this.cfg.hooks?.beforeRequest?.(req)
282
+ for await (const hook of this.cfg.hooks.beforeRequest || []) {
283
+ await hook(req)
284
+ }
245
285
 
246
286
  const res: FetcherResponse<any> = {
247
287
  req,
@@ -330,31 +370,13 @@ export class Fetcher {
330
370
  }),
331
371
  )
332
372
 
333
- // We don't log errors when they are also thrown,
334
- // otherwise it gets logged twice: here, and upstream
335
- // if (this.cfg.logResponse) {
336
- // const { retryAttempt } = res.retryStatus
337
- // logger.error(
338
- // [
339
- // [
340
- // ' <<',
341
- // res.fetchResponse.status,
342
- // signature,
343
- // retryAttempt && `try#${retryAttempt + 1}/${req.retry.count}`,
344
- // _since(started),
345
- // ]
346
- // .filter(Boolean)
347
- // .join(' '),
348
- // _stringifyAny(body),
349
- // ].join('\n'),
350
- // )
351
- // }
352
-
353
373
  await this.processRetry(res)
354
374
  }
355
375
  }
356
376
 
357
- await this.cfg.hooks?.beforeResponse?.(res)
377
+ for await (const hook of this.cfg.hooks.afterResponse || []) {
378
+ await hook(res)
379
+ }
358
380
 
359
381
  return res
360
382
  }
@@ -366,7 +388,9 @@ export class Fetcher {
366
388
  retryStatus.retryStopped = true
367
389
  }
368
390
 
369
- await this.cfg.hooks?.beforeRetry?.(res)
391
+ for await (const hook of this.cfg.hooks.beforeRetry || []) {
392
+ await hook(res)
393
+ }
370
394
 
371
395
  const { count, timeoutMultiplier, timeoutMax } = res.req.retry
372
396
 
@@ -421,10 +445,11 @@ export class Fetcher {
421
445
  console.warn(`Fetcher: baseUrl should not end with /`)
422
446
  cfg.baseUrl = cfg.baseUrl.slice(0, cfg.baseUrl.length - 1)
423
447
  }
424
- const { debug } = cfg
448
+ const { debug = false } = cfg
425
449
 
426
450
  const norm: FetcherNormalizedCfg = _merge(
427
451
  {
452
+ baseUrl: '',
428
453
  url: '',
429
454
  searchParams: {},
430
455
  timeoutSeconds: 30,
@@ -433,6 +458,7 @@ export class Fetcher {
433
458
  retry4xx: false,
434
459
  retry5xx: true,
435
460
  logger: console,
461
+ debug,
436
462
  logRequest: debug,
437
463
  logRequestBody: debug,
438
464
  logResponse: debug,
@@ -442,6 +468,7 @@ export class Fetcher {
442
468
  method: 'get',
443
469
  headers: {},
444
470
  },
471
+ hooks: {},
445
472
  },
446
473
  cfg,
447
474
  )
@@ -469,11 +496,12 @@ export class Fetcher {
469
496
  },
470
497
  init: _merge(
471
498
  { ...this.cfg.init },
472
- opt.init,
499
+ // opt.init,
473
500
  _filterUndefinedValues({
474
501
  method: opt.method,
502
+ credentials: opt.credentials,
475
503
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
476
- }),
504
+ } as RequestInit),
477
505
  ),
478
506
  }
479
507