@naturalcycles/js-lib 14.122.0 → 14.122.1

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.
@@ -119,9 +119,15 @@ class Fetcher {
119
119
  logger.log(req.init.body); // todo: check if we can _inspect it
120
120
  }
121
121
  }
122
- res.fetchResponse = await globalThis.fetch(req.url, req.init);
122
+ try {
123
+ res.fetchResponse = await globalThis.fetch(req.url, req.init);
124
+ }
125
+ catch (err) {
126
+ // For example, CORS error would result in "TypeError: failed to fetch" here
127
+ res.err = err;
128
+ }
123
129
  res.statusFamily = this.getStatusFamily(res);
124
- if (res.fetchResponse.ok) {
130
+ if (res.fetchResponse?.ok) {
125
131
  if (mode === 'json') {
126
132
  // if no body: set responseBody as {}
127
133
  // do not throw a "cannot parse null as Json" error
@@ -150,14 +156,28 @@ class Fetcher {
150
156
  }
151
157
  else {
152
158
  clearTimeout(timeout);
153
- const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
154
- const errObj = (0, error_util_1._anyToErrorObject)(body);
159
+ let errObj;
160
+ if (res.fetchResponse) {
161
+ const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
162
+ errObj = (0, error_util_1._anyToErrorObject)(body);
163
+ }
164
+ else if (res.err) {
165
+ errObj = (0, error_util_1._errorToErrorObject)(res.err);
166
+ }
167
+ else {
168
+ errObj = {};
169
+ }
155
170
  const originalMessage = errObj.message;
156
- errObj.message = [[res.fetchResponse.status, signature].join(' '), originalMessage].join('\n');
171
+ errObj.message = [
172
+ [res.fetchResponse?.status, signature].filter(Boolean).join(' '),
173
+ originalMessage,
174
+ ]
175
+ .filter(Boolean)
176
+ .join('\n');
157
177
  res.err = new http_error_1.HttpError(errObj.message, (0, object_util_1._filterNullishValues)({
158
178
  ...errObj.data,
159
179
  originalMessage,
160
- httpStatusCode: res.fetchResponse.status,
180
+ httpStatusCode: res.fetchResponse?.status || 0,
161
181
  // These properties are provided to be used in e.g custom Sentry error grouping
162
182
  // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
163
183
  // Enabled, cause `data` is not printed by default when error is HttpError
@@ -201,8 +221,13 @@ class Fetcher {
201
221
  if (method === 'post' && !retryPost)
202
222
  return false;
203
223
  const { statusFamily } = res;
224
+ const statusCode = res.fetchResponse?.status || 0;
204
225
  if (statusFamily === 5 && !retry5xx)
205
226
  return false;
227
+ if ([408, 429].includes(statusCode)) {
228
+ // these codes are always retried
229
+ return true;
230
+ }
206
231
  if (statusFamily === 4 && !retry4xx)
207
232
  return false;
208
233
  return true; // default is true
@@ -254,11 +279,12 @@ class Fetcher {
254
279
  logResponseBody: debug,
255
280
  retry: { ...defRetryOptions },
256
281
  init: {
257
- method: 'get',
258
- headers: {},
282
+ method: cfg.method || 'get',
283
+ headers: cfg.headers || {},
284
+ credentials: cfg.credentials,
259
285
  },
260
286
  hooks: {},
261
- }, cfg);
287
+ }, (0, object_util_1._omit)(cfg, ['method', 'credentials', 'headers']));
262
288
  norm.init.headers = (0, object_util_1._mapKeys)(norm.init.headers, k => k.toLowerCase());
263
289
  return norm;
264
290
  }
@@ -271,18 +297,18 @@ class Fetcher {
271
297
  retryPost,
272
298
  retry4xx,
273
299
  retry5xx,
274
- ...(0, object_util_1._omit)(opt, ['method', 'headers']),
300
+ ...(0, object_util_1._omit)(opt, ['method', 'headers', 'credentials']),
275
301
  retry: {
276
302
  ...retry,
277
303
  ...(0, object_util_1._filterUndefinedValues)(opt.retry || {}),
278
304
  },
279
- init: (0, object_util_1._merge)({ ...this.cfg.init },
280
- // opt.init,
281
- (0, object_util_1._filterUndefinedValues)({
282
- method: opt.method,
283
- credentials: opt.credentials,
305
+ init: (0, object_util_1._merge)({
306
+ ...this.cfg.init,
307
+ method: opt.method || this.cfg.init.method,
308
+ credentials: opt.credentials || this.cfg.init.credentials,
309
+ }, {
284
310
  headers: (0, object_util_1._mapKeys)(opt.headers || {}, k => k.toLowerCase()),
285
- })),
311
+ }),
286
312
  };
287
313
  // setup url
288
314
  if (baseUrl) {
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="dom"/>
2
2
  import { __asyncValues } from "tslib";
3
- import { _anyToErrorObject } from '../error/error.util';
3
+ import { _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
4
4
  import { HttpError } from '../error/http.error';
5
5
  import { _clamp } from '../number/number.util';
6
6
  import { _filterNullishValues, _filterUndefinedValues, _mapKeys, _merge, _omit, } from '../object/object.util';
@@ -73,6 +73,7 @@ export class Fetcher {
73
73
  }
74
74
  async rawFetch(url, rawOpt = {}) {
75
75
  var _a, e_1, _b, _c, _d, e_2, _e, _f;
76
+ var _g, _h, _j;
76
77
  const { logger } = this.cfg;
77
78
  const req = this.normalizeOptions(url, rawOpt);
78
79
  const { timeoutSeconds, mode, init: { method }, } = req;
@@ -86,22 +87,22 @@ export class Fetcher {
86
87
  }, timeoutSeconds * 1000);
87
88
  }
88
89
  try {
89
- for (var _g = true, _h = __asyncValues(this.cfg.hooks.beforeRequest || []), _j; _j = await _h.next(), _a = _j.done, !_a;) {
90
- _c = _j.value;
91
- _g = false;
90
+ for (var _k = true, _l = __asyncValues(this.cfg.hooks.beforeRequest || []), _m; _m = await _l.next(), _a = _m.done, !_a;) {
91
+ _c = _m.value;
92
+ _k = false;
92
93
  try {
93
94
  const hook = _c;
94
95
  await hook(req);
95
96
  }
96
97
  finally {
97
- _g = true;
98
+ _k = true;
98
99
  }
99
100
  }
100
101
  }
101
102
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
102
103
  finally {
103
104
  try {
104
- if (!_g && !_a && (_b = _h.return)) await _b.call(_h);
105
+ if (!_k && !_a && (_b = _l.return)) await _b.call(_l);
105
106
  }
106
107
  finally { if (e_1) throw e_1.error; }
107
108
  }
@@ -127,9 +128,15 @@ export class Fetcher {
127
128
  logger.log(req.init.body); // todo: check if we can _inspect it
128
129
  }
129
130
  }
130
- res.fetchResponse = await globalThis.fetch(req.url, req.init);
131
+ try {
132
+ res.fetchResponse = await globalThis.fetch(req.url, req.init);
133
+ }
134
+ catch (err) {
135
+ // For example, CORS error would result in "TypeError: failed to fetch" here
136
+ res.err = err;
137
+ }
131
138
  res.statusFamily = this.getStatusFamily(res);
132
- if (res.fetchResponse.ok) {
139
+ if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
133
140
  if (mode === 'json') {
134
141
  // if no body: set responseBody as {}
135
142
  // do not throw a "cannot parse null as Json" error
@@ -158,11 +165,25 @@ export class Fetcher {
158
165
  }
159
166
  else {
160
167
  clearTimeout(timeout);
161
- const body = _jsonParseIfPossible(await res.fetchResponse.text());
162
- const errObj = _anyToErrorObject(body);
168
+ let errObj;
169
+ if (res.fetchResponse) {
170
+ const body = _jsonParseIfPossible(await res.fetchResponse.text());
171
+ errObj = _anyToErrorObject(body);
172
+ }
173
+ else if (res.err) {
174
+ errObj = _errorToErrorObject(res.err);
175
+ }
176
+ else {
177
+ errObj = {};
178
+ }
163
179
  const originalMessage = errObj.message;
164
- errObj.message = [[res.fetchResponse.status, signature].join(' '), originalMessage].join('\n');
165
- res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: res.fetchResponse.status,
180
+ errObj.message = [
181
+ [(_h = res.fetchResponse) === null || _h === void 0 ? void 0 : _h.status, signature].filter(Boolean).join(' '),
182
+ originalMessage,
183
+ ]
184
+ .filter(Boolean)
185
+ .join('\n');
186
+ res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_j = res.fetchResponse) === null || _j === void 0 ? void 0 : _j.status) || 0,
166
187
  // These properties are provided to be used in e.g custom Sentry error grouping
167
188
  // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
168
189
  // Enabled, cause `data` is not printed by default when error is HttpError
@@ -172,22 +193,22 @@ export class Fetcher {
172
193
  }
173
194
  }
174
195
  try {
175
- for (var _k = true, _l = __asyncValues(this.cfg.hooks.afterResponse || []), _m; _m = await _l.next(), _d = _m.done, !_d;) {
176
- _f = _m.value;
177
- _k = false;
196
+ for (var _o = true, _p = __asyncValues(this.cfg.hooks.afterResponse || []), _q; _q = await _p.next(), _d = _q.done, !_d;) {
197
+ _f = _q.value;
198
+ _o = false;
178
199
  try {
179
200
  const hook = _f;
180
201
  await hook(res);
181
202
  }
182
203
  finally {
183
- _k = true;
204
+ _o = true;
184
205
  }
185
206
  }
186
207
  }
187
208
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
188
209
  finally {
189
210
  try {
190
- if (!_k && !_d && (_e = _l.return)) await _e.call(_l);
211
+ if (!_o && !_d && (_e = _p.return)) await _e.call(_p);
191
212
  }
192
213
  finally { if (e_2) throw e_2.error; }
193
214
  }
@@ -234,13 +255,19 @@ export class Fetcher {
234
255
  * unless there's reason not to (e.g method is POST).
235
256
  */
236
257
  shouldRetry(res) {
258
+ var _a;
237
259
  const { retryPost, retry4xx, retry5xx } = res.req;
238
260
  const { method } = res.req.init;
239
261
  if (method === 'post' && !retryPost)
240
262
  return false;
241
263
  const { statusFamily } = res;
264
+ const statusCode = ((_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status) || 0;
242
265
  if (statusFamily === 5 && !retry5xx)
243
266
  return false;
267
+ if ([408, 429].includes(statusCode)) {
268
+ // these codes are always retried
269
+ return true;
270
+ }
244
271
  if (statusFamily === 4 && !retry4xx)
245
272
  return false;
246
273
  return true; // default is true
@@ -294,11 +321,12 @@ export class Fetcher {
294
321
  logResponseBody: debug,
295
322
  retry: Object.assign({}, defRetryOptions),
296
323
  init: {
297
- method: 'get',
298
- headers: {},
324
+ method: cfg.method || 'get',
325
+ headers: cfg.headers || {},
326
+ credentials: cfg.credentials,
299
327
  },
300
328
  hooks: {},
301
- }, cfg);
329
+ }, _omit(cfg, ['method', 'credentials', 'headers']));
302
330
  norm.init.headers = _mapKeys(norm.init.headers, k => k.toLowerCase());
303
331
  return norm;
304
332
  }
@@ -309,13 +337,9 @@ export class Fetcher {
309
337
  throwHttpErrors,
310
338
  retryPost,
311
339
  retry4xx,
312
- retry5xx }, _omit(opt, ['method', 'headers'])), { retry: Object.assign(Object.assign({}, retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign({}, this.cfg.init),
313
- // opt.init,
314
- _filterUndefinedValues({
315
- method: opt.method,
316
- credentials: opt.credentials,
340
+ retry5xx }, _omit(opt, ['method', 'headers', 'credentials'])), { retry: Object.assign(Object.assign({}, retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign(Object.assign({}, this.cfg.init), { method: opt.method || this.cfg.init.method, credentials: opt.credentials || this.cfg.init.credentials }), {
317
341
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
318
- })) });
342
+ }) });
319
343
  // setup url
320
344
  if (baseUrl) {
321
345
  if (url.startsWith('/')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.122.0",
3
+ "version": "14.122.1",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -1,6 +1,7 @@
1
1
  /// <reference lib="dom"/>
2
2
 
3
- import { _anyToErrorObject } from '../error/error.util'
3
+ import { ErrorObject } from '../error/error.model'
4
+ import { _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
4
5
  import { HttpError } from '../error/http.error'
5
6
  import { CommonLogger } from '../log/commonLogger'
6
7
  import { _clamp } from '../number/number.util'
@@ -333,10 +334,15 @@ export class Fetcher {
333
334
  }
334
335
  }
335
336
 
336
- res.fetchResponse = await globalThis.fetch(req.url, req.init)
337
+ try {
338
+ res.fetchResponse = await globalThis.fetch(req.url, req.init)
339
+ } catch (err) {
340
+ // For example, CORS error would result in "TypeError: failed to fetch" here
341
+ res.err = err as Error
342
+ }
337
343
  res.statusFamily = this.getStatusFamily(res)
338
344
 
339
- if (res.fetchResponse.ok) {
345
+ if (res.fetchResponse?.ok) {
340
346
  if (mode === 'json') {
341
347
  // if no body: set responseBody as {}
342
348
  // do not throw a "cannot parse null as Json" error
@@ -369,12 +375,24 @@ export class Fetcher {
369
375
  } else {
370
376
  clearTimeout(timeout)
371
377
 
372
- const body = _jsonParseIfPossible(await res.fetchResponse.text())
373
- const errObj = _anyToErrorObject(body)
378
+ let errObj: ErrorObject
379
+
380
+ if (res.fetchResponse) {
381
+ const body = _jsonParseIfPossible(await res.fetchResponse.text())
382
+ errObj = _anyToErrorObject(body)
383
+ } else if (res.err) {
384
+ errObj = _errorToErrorObject(res.err)
385
+ } else {
386
+ errObj = {} as ErrorObject
387
+ }
388
+
374
389
  const originalMessage = errObj.message
375
- errObj.message = [[res.fetchResponse.status, signature].join(' '), originalMessage].join(
376
- '\n',
377
- )
390
+ errObj.message = [
391
+ [res.fetchResponse?.status, signature].filter(Boolean).join(' '),
392
+ originalMessage,
393
+ ]
394
+ .filter(Boolean)
395
+ .join('\n')
378
396
 
379
397
  res.err = new HttpError(
380
398
  errObj.message,
@@ -382,7 +400,7 @@ export class Fetcher {
382
400
  _filterNullishValues({
383
401
  ...errObj.data,
384
402
  originalMessage,
385
- httpStatusCode: res.fetchResponse.status,
403
+ httpStatusCode: res.fetchResponse?.status || 0,
386
404
  // These properties are provided to be used in e.g custom Sentry error grouping
387
405
  // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
388
406
  // Enabled, cause `data` is not printed by default when error is HttpError
@@ -437,7 +455,12 @@ export class Fetcher {
437
455
  const { method } = res.req.init
438
456
  if (method === 'post' && !retryPost) return false
439
457
  const { statusFamily } = res
458
+ const statusCode = res.fetchResponse?.status || 0
440
459
  if (statusFamily === 5 && !retry5xx) return false
460
+ if ([408, 429].includes(statusCode)) {
461
+ // these codes are always retried
462
+ return true
463
+ }
441
464
  if (statusFamily === 4 && !retry4xx) return false
442
465
  return true // default is true
443
466
  }
@@ -487,12 +510,13 @@ export class Fetcher {
487
510
  logResponseBody: debug,
488
511
  retry: { ...defRetryOptions },
489
512
  init: {
490
- method: 'get',
491
- headers: {},
513
+ method: cfg.method || 'get',
514
+ headers: cfg.headers || {},
515
+ credentials: cfg.credentials,
492
516
  },
493
517
  hooks: {},
494
518
  },
495
- cfg,
519
+ _omit(cfg, ['method', 'credentials', 'headers']),
496
520
  )
497
521
 
498
522
  norm.init.headers = _mapKeys(norm.init.headers, k => k.toLowerCase())
@@ -511,19 +535,20 @@ export class Fetcher {
511
535
  retryPost,
512
536
  retry4xx,
513
537
  retry5xx,
514
- ..._omit(opt, ['method', 'headers']),
538
+ ..._omit(opt, ['method', 'headers', 'credentials']),
515
539
  retry: {
516
540
  ...retry,
517
541
  ..._filterUndefinedValues(opt.retry || {}),
518
542
  },
519
543
  init: _merge(
520
- { ...this.cfg.init },
521
- // opt.init,
522
- _filterUndefinedValues({
523
- method: opt.method,
524
- credentials: opt.credentials,
544
+ {
545
+ ...this.cfg.init,
546
+ method: opt.method || this.cfg.init.method,
547
+ credentials: opt.credentials || this.cfg.init.credentials,
548
+ },
549
+ {
525
550
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
526
- } as RequestInit),
551
+ } as RequestInit,
527
552
  ),
528
553
  }
529
554