@superhero/http-request 4.0.9 → 4.1.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/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Erik Landvall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -95,11 +95,12 @@ console.log(response.body)
95
95
 
96
96
  Errors are thrown with specific error codes for easier handling:
97
97
 
98
- - `E_HTTP_REQUEST_CLIENT_ERROR`
99
- - `E_HTTP_REQUEST_CLIENT_TIMEOUT`
100
- - `E_HTTP_REQUEST_DOWNSTREAM_ERROR`
101
- - `E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT`
102
- - `E_HTTP_REQUEST_INVALID_RESPONSE_STATUS`
98
+ - `E_HTTP_REQUEST_FAILED`
99
+ - `E_HTTP_REQUEST_CLIENT_ERROR`
100
+ - `E_HTTP_REQUEST_CLIENT_TIMEOUT`
101
+ - `E_HTTP_REQUEST_DOWNSTREAM_ERROR`
102
+ - `E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT`
103
+ - `E_HTTP_REQUEST_INVALID_RESPONSE_STATUS`
103
104
 
104
105
  Example:
105
106
 
@@ -334,10 +335,10 @@ pass 42
334
335
  ----------------------------------------------------------------------------------------
335
336
  file | line % | branch % | funcs % | uncovered lines
336
337
  ----------------------------------------------------------------------------------------
337
- index.js | 94.05 | 91.74 | 92.86 | 496-505 533-535 671-677 702-705 735-755
338
+ index.js | 89.56 | 87.39 | 86.67 | 100-104 137-139 150-153 353-357 361-365
338
339
  index.test.js | 100.00 | 97.67 | 100.00 |
339
340
  ----------------------------------------------------------------------------------------
340
- all files | 96.57 | 94.36 | 97.17 |
341
+ all files | 93.73 | 91.71 | 94.50 |
341
342
  ----------------------------------------------------------------------------------------
342
343
  ```
343
344
 
package/index.js CHANGED
@@ -275,7 +275,7 @@ export default class Request
275
275
  * @throws {Error} E_HTTP_REQUEST_RETRY_HTTP2_RECONNECT
276
276
  * @throws {Error} E_HTTP_REQUEST_RETRY_ERROR
277
277
  */
278
- #fetch(method, options)
278
+ async #fetch(method, options)
279
279
  {
280
280
  if(typeof options === 'string')
281
281
  {
@@ -300,9 +300,19 @@ export default class Request
300
300
  retryOnStatus : []
301
301
  }, this.config, options)
302
302
 
303
- return options.retry
304
- ? this.#resolveRetryLoop(options)
305
- : this.#resolve(options)
303
+ try
304
+ {
305
+ return options.retry
306
+ ? await this.#resolveRetryLoop(options)
307
+ : await this.#resolve(options)
308
+ }
309
+ catch(reason)
310
+ {
311
+ const error = new Error(`Failed request ${method} ${options.url}`)
312
+ error.code = 'E_HTTP_REQUEST_FAILED'
313
+ error.cause = reason
314
+ throw error
315
+ }
306
316
  }
307
317
 
308
318
  /**
@@ -463,9 +473,9 @@ export default class Request
463
473
  */
464
474
  async #resolveRetryLoop(options)
465
475
  {
466
- const errorTrace = []
476
+ const reasons = []
467
477
 
468
- let retry = Math.abs(Math.floor(options.retry)) + 1
478
+ let retry = Math.abs(Math.floor(options.retry))
469
479
 
470
480
  while(retry--)
471
481
  {
@@ -485,30 +495,32 @@ export default class Request
485
495
  return response
486
496
  }
487
497
  }
488
- catch(error)
498
+ catch(reason)
489
499
  {
490
- if(retry === 0)
500
+ if(retry <= 0)
491
501
  {
492
- throw error
502
+ reasons.push(reason)
493
503
  }
494
504
  else
495
505
  {
496
- await this.#resolveRetryLoopError(options, errorTrace, error)
506
+ await this.#resolveRetryLoopError(options, reasons, reason)
497
507
  await wait(options.retryDelay)
498
508
  }
499
509
  }
500
510
  }
501
511
 
502
- if(errorTrace.every((error) => error.code === errorTrace[0].code))
512
+ const
513
+ uniqueReasons = reasons.filter((reason, i) => [i, -1].includes(reasons.lastIndexOf(({ code }) => code === reason.code))),
514
+ reason = uniqueReasons.pop()
515
+
516
+ if(uniqueReasons.length === 1)
503
517
  {
504
- throw errorTrace.pop()
518
+ throw reason
505
519
  }
506
520
  else
507
521
  {
508
- const error = new Error('Multiple types of errors occurred during the retry loop')
509
- error.code = 'E_HTTP_REQUEST_RETRY_ERROR'
510
- error.cause = errorTrace
511
- throw error
522
+ reason.previous = uniqueReasons
523
+ throw reason
512
524
  }
513
525
  }
514
526
 
@@ -516,18 +528,18 @@ export default class Request
516
528
  * Resolves the error in the retry loop.
517
529
  *
518
530
  * @param {RequestOptions} options
519
- * @param {Error[]} errorTrace
520
- * @param {Error} error
531
+ * @param {Error[]} reasons
532
+ * @param {Error} reason
521
533
  *
522
534
  * @returns {Void}
523
535
  */
524
- async #resolveRetryLoopError(options, errorTrace, error)
536
+ async #resolveRetryLoopError(options, reasons, reason)
525
537
  {
526
- switch(error.code)
538
+ switch(reason.code)
527
539
  {
528
540
  case 'E_HTTP_REQUEST_CLIENT_ERROR':
529
541
  {
530
- return errorTrace.push(error)
542
+ return reasons.push(reason)
531
543
  }
532
544
  case 'E_HTTP_REQUEST_HTTP2_SESSION_DESTROYED':
533
545
  case 'E_HTTP_REQUEST_HTTP2_SESSION_CLOSED':
@@ -535,67 +547,68 @@ export default class Request
535
547
  try
536
548
  {
537
549
  await this.reconnect()
550
+ return reasons.push(reason)
538
551
  }
539
552
  catch(reason)
540
553
  {
541
- const reconnectError = new Error(`${reason.message}, retry to reconnect to the server failed`)
542
- reconnectError.code = 'E_HTTP_REQUEST_RETRY_HTTP2_RECONNECT'
543
- reconnectError.cause = reason
554
+ const error = new Error(`${reason.message}, retry to reconnect to the server failed`)
555
+ error.code = 'E_HTTP_REQUEST_RETRY_HTTP2_RECONNECT'
556
+ error.cause = reason
557
+ error.reasons = reasons
544
558
  throw reason
545
559
  }
546
- return errorTrace.push(error)
547
560
  }
548
561
  case 'E_HTTP_REQUEST_CLIENT_TIMEOUT':
549
562
  {
550
563
  if(options.retryOnClientTimeout)
551
564
  {
552
- return errorTrace.push(error)
565
+ return reasons.push(reason)
553
566
  }
554
567
  else
555
568
  {
556
- throw error
569
+ throw reason
557
570
  }
558
571
  }
559
572
  case 'E_HTTP_REQUEST_DOWNSTREAM_ERROR':
560
573
  {
561
574
  if(options.retryOnDownstreamError)
562
575
  {
563
- return errorTrace.push(error)
576
+ return reasons.push(reason)
564
577
  }
565
578
  else
566
579
  {
567
- throw error
580
+ throw reason
568
581
  }
569
582
  }
570
583
  case 'E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT':
571
584
  {
572
585
  if(options.retryOnInvalidResponseBodyFormat)
573
586
  {
574
- return errorTrace.push(error)
587
+ return reasons.push(reason)
575
588
  }
576
589
  else
577
590
  {
578
- throw error
591
+ throw reason
579
592
  }
580
593
  }
581
594
  case 'E_HTTP_REQUEST_INVALID_RESPONSE_STATUS':
582
595
  {
583
596
  if(options.retryOnErrorResponseStatus)
584
597
  {
585
- return errorTrace.push(error)
598
+ return reasons.push(reason)
586
599
  }
587
- else if(options.retryOnStatus.includes(error.response.status))
600
+ else if(options.retryOnStatus.includes(reason.response.status))
588
601
  {
589
- return errorTrace.push(error)
602
+ return reasons.push(reason)
590
603
  }
591
604
  else
592
605
  {
593
- throw error
606
+ throw reason
594
607
  }
595
608
  }
596
609
  default:
597
610
  {
598
- throw error
611
+ throw reason
599
612
  }
600
613
  }
601
614
  }
@@ -603,8 +616,8 @@ export default class Request
603
616
  #createBodyHeaderDelimiter(body, isStreamed)
604
617
  {
605
618
  return isStreamed
606
- ? { 'transfer-encoding': 'chunked' }
607
- : { 'content-length' : body.length }
619
+ ? { 'transfer-encoding' : 'chunked' }
620
+ : { 'content-length' : body?.length || 0 }
608
621
  }
609
622
 
610
623
  /**
package/index.test.js CHANGED
@@ -199,10 +199,10 @@ suite('@superhero/http-request', () =>
199
199
  }
200
200
  await assert.rejects(
201
201
  request.get(options),
202
- (error) => error.code === 'E_HTTP_REQUEST_CLIENT_TIMEOUT',
202
+ (error) => error.cause.code === 'E_HTTP_REQUEST_CLIENT_TIMEOUT',
203
203
  'Should throw a timeout error')
204
204
  })
205
-
205
+
206
206
  test('Rejects invalid JSON response accurately', async () =>
207
207
  {
208
208
  // Alter the server to respond with invalid JSON
@@ -216,7 +216,7 @@ suite('@superhero/http-request', () =>
216
216
  // Make the request
217
217
  await assert.rejects(
218
218
  request.get({ url: '/invalid-json' }),
219
- (error) => error.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT',
219
+ (error) => error.cause.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT',
220
220
  'Should throw a parse error')
221
221
  })
222
222
 
@@ -376,14 +376,14 @@ suite('@superhero/http-request', () =>
376
376
  const options =
377
377
  {
378
378
  url : '/retry-on-status',
379
- retry : 2,
379
+ retry : 2,
380
380
  retryDelay : 100,
381
381
  retryOnStatus : [ 503 ]
382
382
  }
383
383
 
384
384
  await assert.rejects(
385
385
  request.get(options),
386
- (error) => error.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_STATUS',
386
+ (error) => error.cause.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_STATUS',
387
387
  'Should throw an error after the second attempt')
388
388
 
389
389
  assert.equal(attempt, 2, 'Should make exactly 2 attempts')
@@ -401,7 +401,7 @@ suite('@superhero/http-request', () =>
401
401
 
402
402
  await assert.rejects(
403
403
  request.get(options),
404
- (error) => error.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_STATUS',
404
+ (error) => error.cause.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_STATUS',
405
405
  'Should throw an error right away')
406
406
 
407
407
  assert.equal(attempt, 1, 'Should make exactly 1 attempt')
@@ -547,7 +547,7 @@ suite('@superhero/http-request', () =>
547
547
 
548
548
  await assert.rejects(
549
549
  request.get({ url: '/timeout-test', timeout: 100 }),
550
- (error) => error.code === 'E_HTTP_REQUEST_CLIENT_TIMEOUT',
550
+ (error) => error.cause.code === 'E_HTTP_REQUEST_CLIENT_TIMEOUT',
551
551
  'Should throw a timeout error')
552
552
  })
553
553
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superhero/http-request",
3
- "version": "4.0.9",
3
+ "version": "4.1.0",
4
4
  "description": "HTTP(S) request component supporting HTTP 1.1 and HTTP 2.0",
5
5
  "keywords": [
6
6
  "http request",