@mimik/request-retry 4.0.2 → 4.0.4

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/README.md CHANGED
@@ -10,53 +10,66 @@ import rp from '@mimik/request-retry';
10
10
  <a name="module_request-retry..rpRetry"></a>
11
11
 
12
12
  ### request-retry~rpRetry(options) ⇒ <code>Promise</code>
13
- Make a request with retry.
13
+ Make a request with retries.
14
14
 
15
15
  **Kind**: inner method of [<code>request-retry</code>](#module_request-retry)
16
- **Returns**: <code>Promise</code> - .
17
16
  **Category**: async
18
17
  **Throws**:
19
18
 
20
- - <code>Promise</code> Will throw an error generated by `getRichError` encapsulating an error generated by [request-promise](https://www.npmjs.com/package/request-promise) or a TimeoutError.
21
-
22
- The following properties are added to the `options` object under the `retry` property:
23
- - retries `{int}`: maximum number of retries independent to the retryStrategy. The default is `2`. If the value is less than `0` the value is set to `0`, and if the value is more that `15` the value is set to `15`,
24
- - delay `{int}`: in millisecond delay between retries if there is no retryDelay strategy. The default is `1000 ms`. If the value is less than `20 ms` the value is set to `20 ms`, and if the value is more than `30000 ms` the value is set to `30000 ms`,
25
- - delayStrategy `{function}`: function describing the delay strategy. The parameters are (`nbRetry`, `err`, `options`, `correlationId`). Must return a `number` of miliseconds which is greater to 0 ms but less that 30000 ms, if not the value of `delay` will be used,
26
- - logLevel `{object}`: has the following properties:
27
- - *response*: log level to be set for response. The default is `silly` or if the value is wrong it is set to `silly`,
28
- - *error*: log level to be set for error. The default is `silly` or if the value is wrong it is set to `silly`,
29
- - *request*: log level to be set for request: The default is `silly` or if the value is wrong it is set to `silly`,
30
- - *responseDetails*: level of detail when diplaying the response: `count`, `type`, `full`. The default is `type` or if the value is wrong it is set to `type`,
31
- - *responseName*: name to associate with the response. The default is `response`or is the value is not a string or and empty string it is set to `response`,
32
- - timeout `{int}`: in seconds the timeout to be set for the request and retries. If the timeout is reached, a TimeoutError will be generated. The default is `50 seconds`. If the value is less than `10 seconds` the value is set to `10 seconds`, and if the value is more than `120 seconds` the value is set to `120 seconds`,
33
- - retryStrategy `{function}`: function describing the retry strategy. The parameters are (`nbRetry`, `err`, `options`, `correlationId`). Must return a `boolean`, if not the function strategy is ignored.
34
- The following properties are added to the `options` object under the 'metrics' property:
35
- - HTTPRequestDuration `function`: prom-client function to measure the delay of the request,
36
- - url: optional url to be added for labelling the metric. If not present the url of the request will be used.
37
-
38
- The `default` retryStategy is:
39
-
40
- ``` javascript
19
+ - <code>Error</code> An error produced by `getRichError`, wrapping an error from
20
+ [request-promise](https://www.npmjs.com/package/request-promise) or a `TimeoutError`.
21
+
22
+ The following properties may be added to the `options` object under the `retry` property:
23
+ - retries `{int}`: Maximum number of retries, independent of the `retryStrategy`. Default: `2`.
24
+ If the value is `< 0` it is set to `0`; if `> 15` it is set to `15`.
25
+ - delay `{int}`: Delay between retries in milliseconds when no `delayStrategy` is provided. Default: `1000`.
26
+ If the value is `< 20` it is set to `20`; if `> 30000` it is set to `30000`.
27
+ - delayStrategy `{function}`: A function that returns the delay (in milliseconds) to wait before the next retry.
28
+ Signature: `(nbRetry, err, options, correlationId)`. Must return a number `> 0` and `< 30000`; otherwise `delay` is used.
29
+ - logLevel `{object}`: Controls logging behavior:
30
+ - *response*: Log level for responses. Default: `silly` (invalid values fall back to `silly`).
31
+ - *error*: Log level for errors. Default: `silly` (invalid values fall back to `silly`).
32
+ - *request*: Log level for requests. Default: `silly` (invalid values fall back to `silly`).
33
+ - *responseDetails*: Detail level when displaying the response: `count`, `type`, or `full`. Default: `type`
34
+ (invalid values fall back to `type`).
35
+ - *responseName*: Label associated with the response. Default: `response`
36
+ (non-string or empty values fall back to `response`).
37
+ - timeout `{int}`: Request timeout (in seconds) applied to the initial request and all retries.
38
+ If reached, a `TimeoutError` is thrown. Default: `50`. Values `< 10` are set to `10`; values `> 120` are set to `120`.
39
+ - retryStrategy `{function}`: A function that decides whether to retry. Signature:
40
+ `(nbRetry, err, options, correlationId)`. Must return a boolean; otherwise it is ignored.
41
+
42
+ The following properties may be added to the `options` object under the `metrics` property:
43
+ - HTTPRequestDuration `{function}`: A `prom-client` metric function to measure request duration.
44
+ - url `{string}`: Optional URL label for the metric. If omitted, the request URL is used.
45
+
46
+ The **default** `retryStrategy` is:
47
+
48
+ ```javascript
41
49
  defaultRetry = (...args) => {
42
50
  const { statusCode } = args[1]; // err
43
-
44
- return (statusCode && (Math.floor(statusCode / 100) === 5 || statusCode === 429)) || (!statusCode && !args[1].message.includes('Invalid URI'));
51
+ // Retry on 5xx and 429. If there is no statusCode, retry unless it's an "Invalid URI" error.
52
+ return (statusCode && (Math.floor(statusCode / 100) === 5 || statusCode === 429))
53
+ || (!statusCode && !args[1].message.includes('Invalid URI'));
45
54
  };
46
55
  ```
47
- If logLevel is empty, request and response will be logged as info and the response will be in response property with full details. Error on the request will generate a warning. If logLevel is not empty but not complete, logLevel will take control over default.
48
56
 
49
- If not alredy set,the user agent will the set to `mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`;
57
+ If `logLevel` is empty, requests and responses are logged at `info`, the response is logged with full details
58
+ under the `response` property, and request errors generate a `warn`. If `logLevel` is provided but incomplete,
59
+ the provided keys override the defaults.
60
+
61
+ If not already set, the `User-Agent` header is set to:
62
+ `mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`.
50
63
 
51
- To facilitate the transition from request-promise the following action are taken on options:
52
- - `uri` takes precednce on `url`
53
- - `body` takes precedence on `json` if `json` is an object and are used to assign `data`
54
- - `qs` is used to assign to `params`
64
+ To ease migration from `request-promise`, the following adjustments are applied to `options`:
65
+ - `uri` takes precedence over `url`.
66
+ - If `json` is an object, `body` takes precedence over `json`; both are mapped to Axios `data`.
67
+ - `qs` is mapped to Axios `params`.
55
68
 
56
69
  **Requires**: <code>module:@mimik/sumologic-winston-logger</code>, <code>module:@mimik/response-helper</code>
57
- **Fulfil**: <code>object</code> - Response of the [axios](https://www.npmjs.com/package/axios) response with option `resolveWithFullResponse` set to true otherwise only `response.data` is returned.
70
+ **Fulfil**: <code>object</code> - The Axios response when `resolveWithFullResponse` is `true`; otherwise `response.data`.
58
71
 
59
72
  | Param | Type | Description |
60
73
  | --- | --- | --- |
61
- | options | <code>object</code> | Options for the request. Similar to [axios](https://www.npmjs.com/package/axios) options. `validateStatus` options is disabled, an error will be created for statusCode outside of [200, 300[. The options `resolveWithFullResponse` is added and if set to true, the response will be a full axios response. If set to false or missing only `reponse.data` will be returned. |
74
+ | options | <code>object</code> | Options for the request (similar to [axios](https://www.npmjs.com/package/axios)). The `validateStatus` option is disabled: an error is thrown for status codes outside **[200, 300)**. An additional option, `resolveWithFullResponse`, controls the return shape: when `true`, the full Axios response object is returned; when `false` or omitted, only `response.data` is returned. |
62
75
 
package/eslint.config.js CHANGED
@@ -10,6 +10,9 @@ const MAX_LINES_IN_FUNCTION = 150;
10
10
  const MAX_STATEMENTS_IN_FUNCTION = 45;
11
11
  const MIN_KEYS_IN_OBJECT = 10;
12
12
  const MAX_COMPLEXITY = 30;
13
+ const ECMA_VERSION = 2022;
14
+ const MAX_DEPTH = 6;
15
+ const ALLOWED_CONSTANTS = [0, 1, -1];
13
16
 
14
17
  export default [
15
18
  {
@@ -23,7 +26,7 @@ export default [
23
26
  processDoc,
24
27
  },
25
28
  languageOptions: {
26
- ecmaVersion: 2022,
29
+ ecmaVersion: ECMA_VERSION,
27
30
  globals: {
28
31
  console: 'readonly',
29
32
  describe: 'readonly',
@@ -35,6 +38,7 @@ export default [
35
38
  rules: {
36
39
  '@stylistic/brace-style': ['warn', 'stroustrup', { allowSingleLine: true }],
37
40
  '@stylistic/line-comment-position': ['off'],
41
+ '@stylistic/max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true, ignoreStrings: true, ignoreRegExpLiterals: true }],
38
42
  '@stylistic/semi': ['error', 'always'],
39
43
  'capitalized-comments': ['off'],
40
44
  'complexity': ['error', MAX_COMPLEXITY],
@@ -44,20 +48,22 @@ export default [
44
48
  'import/no-unresolved': ['error', { amd: true, caseSensitiveStrict: true, commonjs: true }],
45
49
  'init-declarations': ['off'],
46
50
  'linebreak-style': ['off'],
47
- 'max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true }],
48
- 'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true }],
49
- 'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true }],
51
+ 'max-depth': ['error', MAX_DEPTH],
52
+ 'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true, skipBlankLines: true }],
53
+ 'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true, skipBlankLines: true }],
50
54
  'max-params': ['error', MAX_FUNCTION_PARAMETERS],
51
55
  'max-statements': ['warn', MAX_STATEMENTS_IN_FUNCTION],
52
- 'no-confusing-arrow': ['off'], // arrow isnt confusing
56
+ 'no-confusing-arrow': ['off'],
53
57
  'no-inline-comments': ['off'],
58
+ 'no-magic-numbers': ['error', { ignore: ALLOWED_CONSTANTS, enforceConst: true, detectObjects: true }],
54
59
  'no-process-env': ['error'],
55
60
  'no-ternary': ['off'],
56
61
  'no-undefined': ['off'],
57
62
  'one-var': ['error', 'never'],
58
63
  'processDoc/validate-document-env': ['error'],
59
64
  'quotes': ['warn', 'single'],
60
- 'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false }],
65
+ 'sort-imports': ['error', { allowSeparatedGroups: true }],
66
+ 'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false, allowLineSeparatedGroups: true }],
61
67
  },
62
68
  },
63
69
  ];
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { OK, SYSTEM_ERROR } from './lib/common.js';
1
2
  import {
2
3
  calculateDelay,
3
4
  isRetry,
@@ -7,7 +8,7 @@ import {
7
8
  import {
8
9
  clearTimeout,
9
10
  setTimeout,
10
- } from 'timers';
11
+ } from 'node:timers';
11
12
  import {
12
13
  getCorrelationId,
13
14
  setUserAgent,
@@ -15,7 +16,7 @@ import {
15
16
  import Promise from 'bluebird';
16
17
  import { getRichError } from '@mimik/response-helper';
17
18
  import logger from '@mimik/sumologic-winston-logger';
18
- import process from 'process';
19
+ import process from 'node:process';
19
20
  import { rp } from './lib/rp-axios-wrapper.js';
20
21
 
21
22
  const DEFAULT_RESPONSE_NAME = 'response';
@@ -26,13 +27,8 @@ const DEFAULT_LOGLEVEL_REQUEST = 'info';
26
27
 
27
28
  const MILLI_SECONDS = 0;
28
29
  const MILLI_NANO_SECONDS = 1;
29
- const INCR = 1;
30
30
  const MILLI = 1000;
31
31
  const NANO = 1e6;
32
- const EMPTY = 0;
33
- const NONE = 0;
34
- const OK_RESPONSE = 200;
35
- const SYSTEM_ERROR = 500;
36
32
 
37
33
  Promise.config({ cancellation: true });
38
34
 
@@ -45,50 +41,67 @@ Promise.config({ cancellation: true });
45
41
  */
46
42
 
47
43
  /**
48
- * Make a request with retry.
44
+ * Make a request with retries.
49
45
  *
50
46
  * @function rpRetry
51
47
  * @requires @mimik/sumologic-winston-logger
52
48
  * @requires @mimik/response-helper
53
49
  * @category async
54
- * @param {object} options - Options for the request. Similar to [axios](https://www.npmjs.com/package/axios) options. `validateStatus` options is disabled, an error will be created for statusCode outside of [200, 300[. The options `resolveWithFullResponse` is added and if set to true, the response will be a full axios response. If set to false or missing only `reponse.data` will be returned.
55
- * @return {Promise}.
56
- * @fulfil {object} - Response of the [axios](https://www.npmjs.com/package/axios) response with option `resolveWithFullResponse` set to true otherwise only `response.data` is returned.
57
- * @throws {Promise} Will throw an error generated by `getRichError` encapsulating an error generated by [request-promise](https://www.npmjs.com/package/request-promise) or a TimeoutError.
50
+ * @param {object} options - Options for the request (similar to [axios](https://www.npmjs.com/package/axios)).
51
+ * The `validateStatus` option is disabled: an error is thrown for status codes outside **[200, 300)**.
52
+ * An additional option, `resolveWithFullResponse`, controls the return shape:
53
+ * when `true`, the full Axios response object is returned; when `false` or omitted, only `response.data` is returned.
54
+ * @return {Promise}
55
+ * @fulfil {object} - The Axios response when `resolveWithFullResponse` is `true`; otherwise `response.data`.
56
+ * @throws {Error} An error produced by `getRichError`, wrapping an error from
57
+ * [request-promise](https://www.npmjs.com/package/request-promise) or a `TimeoutError`.
58
58
  *
59
- * The following properties are added to the `options` object under the `retry` property:
60
- * - retries `{int}`: maximum number of retries independent to the retryStrategy. The default is `2`. If the value is less than `0` the value is set to `0`, and if the value is more that `15` the value is set to `15`,
61
- * - delay `{int}`: in millisecond delay between retries if there is no retryDelay strategy. The default is `1000 ms`. If the value is less than `20 ms` the value is set to `20 ms`, and if the value is more than `30000 ms` the value is set to `30000 ms`,
62
- * - delayStrategy `{function}`: function describing the delay strategy. The parameters are (`nbRetry`, `err`, `options`, `correlationId`). Must return a `number` of miliseconds which is greater to 0 ms but less that 30000 ms, if not the value of `delay` will be used,
63
- * - logLevel `{object}`: has the following properties:
64
- * - *response*: log level to be set for response. The default is `silly` or if the value is wrong it is set to `silly`,
65
- * - *error*: log level to be set for error. The default is `silly` or if the value is wrong it is set to `silly`,
66
- * - *request*: log level to be set for request: The default is `silly` or if the value is wrong it is set to `silly`,
67
- * - *responseDetails*: level of detail when diplaying the response: `count`, `type`, `full`. The default is `type` or if the value is wrong it is set to `type`,
68
- * - *responseName*: name to associate with the response. The default is `response`or is the value is not a string or and empty string it is set to `response`,
69
- * - timeout `{int}`: in seconds the timeout to be set for the request and retries. If the timeout is reached, a TimeoutError will be generated. The default is `50 seconds`. If the value is less than `10 seconds` the value is set to `10 seconds`, and if the value is more than `120 seconds` the value is set to `120 seconds`,
70
- * - retryStrategy `{function}`: function describing the retry strategy. The parameters are (`nbRetry`, `err`, `options`, `correlationId`). Must return a `boolean`, if not the function strategy is ignored.
71
- * The following properties are added to the `options` object under the 'metrics' property:
72
- * - HTTPRequestDuration `function`: prom-client function to measure the delay of the request,
73
- * - url: optional url to be added for labelling the metric. If not present the url of the request will be used.
59
+ * The following properties may be added to the `options` object under the `retry` property:
60
+ * - retries `{int}`: Maximum number of retries, independent of the `retryStrategy`. Default: `2`.
61
+ * If the value is `< 0` it is set to `0`; if `> 15` it is set to `15`.
62
+ * - delay `{int}`: Delay between retries in milliseconds when no `delayStrategy` is provided. Default: `1000`.
63
+ * If the value is `< 20` it is set to `20`; if `> 30000` it is set to `30000`.
64
+ * - delayStrategy `{function}`: A function that returns the delay (in milliseconds) to wait before the next retry.
65
+ * Signature: `(nbRetry, err, options, correlationId)`. Must return a number `> 0` and `< 30000`; otherwise `delay` is used.
66
+ * - logLevel `{object}`: Controls logging behavior:
67
+ * - *response*: Log level for responses. Default: `silly` (invalid values fall back to `silly`).
68
+ * - *error*: Log level for errors. Default: `silly` (invalid values fall back to `silly`).
69
+ * - *request*: Log level for requests. Default: `silly` (invalid values fall back to `silly`).
70
+ * - *responseDetails*: Detail level when displaying the response: `count`, `type`, or `full`. Default: `type`
71
+ * (invalid values fall back to `type`).
72
+ * - *responseName*: Label associated with the response. Default: `response`
73
+ * (non-string or empty values fall back to `response`).
74
+ * - timeout `{int}`: Request timeout (in seconds) applied to the initial request and all retries.
75
+ * If reached, a `TimeoutError` is thrown. Default: `50`. Values `< 10` are set to `10`; values `> 120` are set to `120`.
76
+ * - retryStrategy `{function}`: A function that decides whether to retry. Signature:
77
+ * `(nbRetry, err, options, correlationId)`. Must return a boolean; otherwise it is ignored.
74
78
  *
75
- * The `default` retryStategy is:
79
+ * The following properties may be added to the `options` object under the `metrics` property:
80
+ * - HTTPRequestDuration `{function}`: A `prom-client` metric function to measure request duration.
81
+ * - url `{string}`: Optional URL label for the metric. If omitted, the request URL is used.
76
82
  *
77
- *``` javascript
83
+ * The **default** `retryStrategy` is:
84
+ *
85
+ * ```javascript
78
86
  * defaultRetry = (...args) => {
79
87
  * const { statusCode } = args[1]; // err
80
- *
81
- * return (statusCode && (Math.floor(statusCode / 100) === 5 || statusCode === 429)) || (!statusCode && !args[1].message.includes('Invalid URI'));
88
+ * // Retry on 5xx and 429. If there is no statusCode, retry unless it's an "Invalid URI" error.
89
+ * return (statusCode && (Math.floor(statusCode / 100) === 5 || statusCode === 429))
90
+ * || (!statusCode && !args[1].message.includes('Invalid URI'));
82
91
  * };
83
- *```
84
- * If logLevel is empty, request and response will be logged as info and the response will be in response property with full details. Error on the request will generate a warning. If logLevel is not empty but not complete, logLevel will take control over default.
92
+ * ```
93
+ *
94
+ * If `logLevel` is empty, requests and responses are logged at `info`, the response is logged with full details
95
+ * under the `response` property, and request errors generate a `warn`. If `logLevel` is provided but incomplete,
96
+ * the provided keys override the defaults.
85
97
  *
86
- * If not alredy set,the user agent will the set to `mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`;
98
+ * If not already set, the `User-Agent` header is set to:
99
+ * `mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`.
87
100
  *
88
- * To facilitate the transition from request-promise the following action are taken on options:
89
- * - `uri` takes precednce on `url`
90
- * - `body` takes precedence on `json` if `json` is an object and are used to assign `data`
91
- * - `qs` is used to assign to `params`
101
+ * To ease migration from `request-promise`, the following adjustments are applied to `options`:
102
+ * - `uri` takes precedence over `url`.
103
+ * - If `json` is an object, `body` takes precedence over `json`; both are mapped to Axios `data`.
104
+ * - `qs` is mapped to Axios `params`.
92
105
  */
93
106
  export const rpRetry = (origOptions) => {
94
107
  const startHrTime = process.hrtime();
@@ -101,7 +114,7 @@ export const rpRetry = (origOptions) => {
101
114
  const criteria = validateOptions(options, correlationId);
102
115
  const { logLevel } = criteria;
103
116
  const logLevelLength = Object.keys(logLevel).length;
104
- let nbRetries = NONE;
117
+ let nbRetries = 0;
105
118
  let mainTimeout;
106
119
  const measure = (statusCode) => {
107
120
  if (metrics && metrics.HTTPRequestDuration) {
@@ -127,7 +140,7 @@ export const rpRetry = (origOptions) => {
127
140
  info[logLevel.responseName] = setResponse(response, logLevel.responseDetails);
128
141
  logger[logLevel.response]('request response', info, correlationId);
129
142
  }
130
- else if (logLevelLength === EMPTY) {
143
+ else if (logLevelLength === 0) {
131
144
  info[DEFAULT_RESPONSE_NAME] = setResponse(response, DEFAULT_LOGLEVEL_DETAILS);
132
145
  logger[DEFAULT_LOGLEVEL_RESPONSE]('request response', info, correlationId);
133
146
  }
@@ -135,16 +148,16 @@ export const rpRetry = (origOptions) => {
135
148
  })
136
149
  .catch((err) => {
137
150
  if ((nbRetry < criteria.retries) && isRetry(options, nbRetry, criteria, err, correlationId)) {
138
- nbRetries = nbRetry + INCR;
151
+ nbRetries = nbRetry + 1;
139
152
  errors.push(err);
140
153
  const delayPromise = Promise.delay(calculateDelay(options, nbRetry, criteria, err, correlationId));
141
154
 
142
155
  delayPromises.unshift(delayPromise);
143
- return delayPromise.then(() => retryProcess(nbRetry + INCR));
156
+ return delayPromise.then(() => retryProcess(nbRetry + 1));
144
157
  }
145
158
  const error = err;
146
159
 
147
- if (nbRetries !== NONE) {
160
+ if (nbRetries !== 0) {
148
161
  if (!error.info) error.info = {};
149
162
  error.info.retry = {
150
163
  nbRetries,
@@ -156,18 +169,18 @@ export const rpRetry = (origOptions) => {
156
169
  'request error response',
157
170
  { options: optionsInfo },
158
171
  error,
159
- logLevel.error || ((logLevelLength === EMPTY) ? DEFAULT_LOGLEVEL_ERROR : undefined),
172
+ logLevel.error || ((logLevelLength === 0) ? DEFAULT_LOGLEVEL_ERROR : undefined),
160
173
  correlationId,
161
174
  );
162
175
  });
163
176
 
164
- const retryPromise = retryProcess(NONE);
177
+ const retryPromise = retryProcess(0);
165
178
  const mainTimeoutPromise = new Promise((resolve, reject) => {
166
179
  mainTimeout = setTimeout(() => {
167
180
  delayPromises.forEach(delayPromise => delayPromise.cancel(`retry timeout delay, ${criteria.timeout}, ${nbRetries}`));
168
181
  let error = new Error('retry timeout');
169
182
 
170
- if (nbRetries !== NONE) {
183
+ if (nbRetries !== 0) {
171
184
  error.info = { retry: { nbRetries, errors } };
172
185
  }
173
186
  error.name = 'TimeoutError';
@@ -176,7 +189,7 @@ export const rpRetry = (origOptions) => {
176
189
  'request error response',
177
190
  { options: optionsInfo },
178
191
  error,
179
- logLevel.error || ((logLevelLength === EMPTY) ? DEFAULT_LOGLEVEL_ERROR : undefined),
192
+ logLevel.error || ((logLevelLength === 0) ? DEFAULT_LOGLEVEL_ERROR : undefined),
180
193
  correlationId,
181
194
  );
182
195
  reject(error);
@@ -184,12 +197,12 @@ export const rpRetry = (origOptions) => {
184
197
  });
185
198
 
186
199
  if (logLevel.request) logger[logLevel.request]('making a request', { options: optionsInfo, criteria }, correlationId);
187
- else if (logLevelLength === EMPTY) logger[DEFAULT_LOGLEVEL_REQUEST]('making a request', { options: optionsInfo, criteria }, correlationId);
200
+ else if (logLevelLength === 0) logger[DEFAULT_LOGLEVEL_REQUEST]('making a request', { options: optionsInfo, criteria }, correlationId);
188
201
  return Promise.race([retryPromise, mainTimeoutPromise])
189
202
  .then((result) => {
190
203
  mainTimeoutPromise.cancel('main timeout');
191
204
  clearTimeout(mainTimeout);
192
- measure(OK_RESPONSE);
205
+ measure(OK);
193
206
  return result;
194
207
  })
195
208
  .catch((err) => {
package/lib/common.js ADDED
@@ -0,0 +1,13 @@
1
+ const PARAMETER_ERROR = 400;
2
+ const TOO_MANY_REQUESTS_ERROR = 429;
3
+ const SYSTEM_ERROR = 500;
4
+ const OK = 200;
5
+ const CREATED = 201;
6
+
7
+ export {
8
+ PARAMETER_ERROR,
9
+ TOO_MANY_REQUESTS_ERROR,
10
+ SYSTEM_ERROR,
11
+ OK,
12
+ CREATED,
13
+ };
@@ -1,6 +1,8 @@
1
1
  import axios from 'axios';
2
- import clone from 'lodash.clone';
3
2
  import { getRichError } from '@mimik/response-helper';
3
+ import rfdc from 'rfdc';
4
+
5
+ const clone = rfdc();
4
6
 
5
7
  const rp = (origOptions) => {
6
8
  const options = clone(origOptions);
package/lib/validate.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { TOO_MANY_REQUESTS_ERROR } from './common.js';
1
2
  import logger from '@mimik/sumologic-winston-logger';
2
3
 
3
4
  const DETAILS = ['full', 'type', 'count'];
@@ -7,15 +8,13 @@ const [, , , , , DEFAULT_LOGLEVEL/* 'silly' */] = logger.LEVELS;
7
8
  const DEFAULT_RETRIES = 2;
8
9
  const MIN_RETRIES = 0;
9
10
  const MAX_RETRIES = 15;
10
- const DEFAULT_DELAY = 1000; // in ms
11
- const MIN_DELAY = 20; // in ms
12
- const MAX_DELAY = 30000; // in ms
11
+ const DEFAULT_DELAY = 1000; // in millisecond
12
+ const MIN_DELAY = 20; // in millisecond
13
+ const MAX_DELAY = 30000; // in millisecond
13
14
  const NO_DELAY = 0;
14
- const DEFAULT_TIMEOUT = 50; // in seconds
15
- const MIN_TIMEOUT = 10; // in seconds
16
- const MAX_TIMEOUT = 120; // in seconds
17
- const TOO_MANY_REQUESTS_ERROR = 429;
18
- const EMPTY = 0;
15
+ const DEFAULT_TIMEOUT = 50; // in second
16
+ const MIN_TIMEOUT = 10; // in second
17
+ const MAX_TIMEOUT = 120; // in second
19
18
  const SYSTEM_RANGE = 5;
20
19
  const SYSTEM_RANGE_EXTRACT = 100;
21
20
  const ARG_ERROR = 1;
@@ -80,7 +79,7 @@ const validateOptions = (options, correlationId) => {
80
79
  };
81
80
  const validateResponseName = (val, name, defaultValue) => {
82
81
  if (!val && val !== '') return defaultValue;
83
- if (typeof val !== 'string' || val.length === EMPTY || val.trim().length === EMPTY) {
82
+ if (typeof val !== 'string' || val.length === 0 || val.trim().length === 0) {
84
83
  logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val, defaultValue }, correlationId);
85
84
  return defaultValue;
86
85
  }
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable no-console */
2
+ import { OK } from '../lib/common.js';
2
3
  import bodyParser from 'body-parser';
3
4
  import express from 'express';
4
5
 
@@ -8,9 +9,9 @@ const app = express();
8
9
 
9
10
  app.use(bodyParser.json());
10
11
  app.get('/success', (req, res) => {
11
- res.statusCode = 200;
12
+ res.statusCode = OK;
12
13
 
13
- res.send({ statusCode: 200 });
14
+ res.send({ statusCode: OK });
14
15
  });
15
16
 
16
17
  app.listen(PORT, () => {
@@ -3,6 +3,8 @@ import './testEnv';
3
3
  import { rpRetry } from '../index.js';
4
4
 
5
5
  const DELAY = 1000;
6
+ const RETRY_TIMEOUT = 10;
7
+ const RETRY_RETRIES = 2;
6
8
 
7
9
  const correlationId = `--request-retry-test--/${new Date().toISOString()}`;
8
10
 
@@ -17,8 +19,8 @@ const options = {
17
19
  };
18
20
 
19
21
  options.retry.delayStrategy = () => DELAY;
20
- options.retry.timeout = 10;
21
- options.retry.retries = 2;
22
+ options.retry.timeout = RETRY_TIMEOUT;
23
+ options.retry.retries = RETRY_RETRIES;
22
24
 
23
25
  rpRetry(options)
24
26
  .catch(err => console.log(err));
@@ -1,24 +1,27 @@
1
1
  /* eslint-disable no-console */
2
+ import { CREATED, OK, SYSTEM_ERROR } from '../lib/common.js';
2
3
  import bodyParser from 'body-parser';
3
4
  import express from 'express';
4
- import { setTimeout } from 'timers';
5
+ import { setTimeout } from 'node:timers';
5
6
 
6
- const NONE = 0;
7
- const INCR = 1;
7
+ const PORT = 9080;
8
+ const GET_NB_RETRY = 400;
9
+ const GET_TIMEOUT = 4000;
8
10
 
9
11
  const app = express();
12
+
10
13
  const config = {
11
- port: 9080,
14
+ port: PORT,
12
15
  path: '/retry',
13
16
  base: '/test',
14
17
  get: {
15
- nbRetry: 400,
16
- success: 200,
17
- error: 500,
18
- timeout: 4000,
18
+ nbRetry: GET_NB_RETRY,
19
+ success: OK,
20
+ error: SYSTEM_ERROR,
21
+ timeout: GET_TIMEOUT,
19
22
  },
20
23
  post: {
21
- success: 201,
24
+ success: CREATED,
22
25
  },
23
26
  };
24
27
  let nbRequest = 0;
@@ -27,7 +30,7 @@ let message;
27
30
 
28
31
  app.use(bodyParser.json());
29
32
  app.get(`${config.base}${config.path}`, (req, res) => {
30
- if (nbRequest === NONE) message = 'Recieved first request';
33
+ if (nbRequest === 0) message = 'Recieved first request';
31
34
  else {
32
35
  const timeLap = Date.now() - time;
33
36
 
@@ -41,7 +44,7 @@ app.get(`${config.base}${config.path}`, (req, res) => {
41
44
  res.send({ statusCode: config.get.success });
42
45
  return;
43
46
  }
44
- nbRequest += INCR;
47
+ nbRequest += 1;
45
48
  setTimeout(() => {
46
49
  res.statusCode = config.get.error;
47
50
  res.send({ statusCode: config.get.error });
@@ -4,20 +4,19 @@ import {
4
4
  setTimeout,
5
5
  } from 'timers';
6
6
 
7
- const INCR = 1;
8
7
  const MAX_INCR = 100000000;
9
- const NO_RESULT = 0;
8
+ const MAIN_TIMEOUT = 1; // in millisecond
10
9
 
11
10
  const doSomething = () => {
12
11
  let add = 0;
13
12
 
14
- for (let i = 0; i < MAX_INCR; i += INCR) {
15
- add += INCR;
13
+ for (let i = 0; i < MAX_INCR; i += 1) {
14
+ add += 1;
16
15
  console.log(add);
17
16
  }
18
17
  return add;
19
18
  };
20
- const timeout = 1; // ms
19
+ const timeout = MAIN_TIMEOUT;
21
20
 
22
21
  const race = () => {
23
22
  let result;
@@ -30,7 +29,7 @@ const race = () => {
30
29
  }
31
30
  catch (err) {
32
31
  console.log(err);
33
- result = NO_RESULT;
32
+ result = 0;
34
33
  }
35
34
  clearTimeout(mainTimeout);
36
35
  return result;
@@ -2,6 +2,8 @@
2
2
  import '../test/testEnv.js';
3
3
  import { rpRetry } from '../index.js';
4
4
 
5
+ const TIMEOUT = 20000; // in millisecond
6
+
5
7
  const options = {
6
8
  method: 'GET',
7
9
  url: 'http://api.swaggerhub.com/apis/mimik/Boxes/1.2.1', // 'http://www.google.com', // 'http://blah.com', // 'http://localhost:9070/test/retry', // 'http://www.google.com', // 'http://localhost:8080/mIT/v1/admin/healthCheck1', // 'http://blah.com'
@@ -9,7 +11,7 @@ const options = {
9
11
  'x-correlation-id': '---test-request-retry---',
10
12
  },
11
13
  json: true,
12
- timeout: 20000,
14
+ timeout: TIMEOUT,
13
15
  /*
14
16
  * retry: {
15
17
  * all default: maxRetry: 2, retryDelay: 1000ms, maxTimeout: 50s, logLevel: 3 silly, type response, retryDelayStrategy: setDelay, retryStrategy: defaultRetry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mimik/request-retry",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "Request retry wrapping axios",
5
5
  "main": "./index.js",
6
6
  "type": "module",
@@ -30,26 +30,26 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@mimik/request-helper": "^2.0.2",
33
- "@mimik/response-helper": "^4.0.3",
34
- "@mimik/sumologic-winston-logger": "^2.0.3",
35
- "axios": "1.10.0",
33
+ "@mimik/response-helper": "^4.0.6",
34
+ "@mimik/sumologic-winston-logger": "^2.1.6",
35
+ "axios": "1.12.1",
36
36
  "bluebird": "3.7.2",
37
- "lodash.clone": "4.5.0"
37
+ "rfdc": "1.4.1"
38
38
  },
39
39
  "devDependencies": {
40
- "@eslint/js": "9.31.0",
40
+ "@eslint/js": "9.35.0",
41
41
  "@mimik/eslint-plugin-document-env": "^2.0.8",
42
- "@stylistic/eslint-plugin": "5.2.0",
42
+ "@stylistic/eslint-plugin": "5.3.1",
43
43
  "acorn": "8.15.0",
44
44
  "body-parser": "2.2.0",
45
45
  "c8": "10.1.3",
46
- "chai": "5.2.1",
47
- "eslint": "9.31.0",
46
+ "chai": "6.0.1",
47
+ "eslint": "9.35.0",
48
48
  "eslint-plugin-import": "2.32.0",
49
49
  "express": "5.1.0",
50
50
  "husky": "9.1.7",
51
51
  "jsdoc-to-markdown": "9.1.2",
52
- "mocha": "11.7.1",
52
+ "mocha": "11.7.2",
53
53
  "mochawesome": "7.1.3",
54
54
  "sinon": "21.0.0"
55
55
  }
package/test/retryMock.js CHANGED
@@ -1,26 +1,27 @@
1
1
  /* eslint-disable no-console */
2
+ import { CREATED, OK, SYSTEM_ERROR } from '../lib/common.js';
2
3
  import bodyParser from 'body-parser';
3
4
  import express from 'express';
4
- import process from 'process';
5
- import { setTimeout } from 'timers';
5
+ import process from 'node:process';
6
+ import { setTimeout } from 'node:timers';
6
7
 
7
- const NONE = 0;
8
- const INCR = 1;
9
8
  const TIMEOUT = 3000;
10
- const EXIT_NORMAL = 0;
9
+ const EXIT_OK = 0;
10
+ const PORT = 9070;
11
+ const GET_NB_RETRY = 400;
11
12
 
12
13
  const app = express();
13
14
  const config = {
14
- port: 9070,
15
+ port: PORT,
15
16
  path: '/retry',
16
17
  base: '/test',
17
18
  get: {
18
- nbRetry: 400,
19
- success: 200,
20
- error: 500,
19
+ nbRetry: GET_NB_RETRY,
20
+ success: OK,
21
+ error: SYSTEM_ERROR,
21
22
  },
22
23
  post: {
23
- success: 201,
24
+ success: CREATED,
24
25
  },
25
26
  };
26
27
  let nbRequest = 0;
@@ -29,7 +30,7 @@ let message;
29
30
 
30
31
  app.use(bodyParser.json());
31
32
  app.get(`${config.base}${config.path}`, (req, res) => {
32
- if (nbRequest === NONE) message = 'Recieved first request';
33
+ if (nbRequest === 0) message = 'Recieved first request';
33
34
  else {
34
35
  const timeLap = Date.now() - time;
35
36
 
@@ -43,7 +44,7 @@ app.get(`${config.base}${config.path}`, (req, res) => {
43
44
  res.send({ statusCode: config.get.success });
44
45
  return;
45
46
  }
46
- nbRequest += INCR;
47
+ nbRequest += 1;
47
48
  res.statusCode = config.get.error;
48
49
 
49
50
  res.send({ statusCode: config.get.error });
@@ -58,7 +59,7 @@ app.get('/stop', (req, res) => {
58
59
  res.statusCode = config.get.success;
59
60
  res.send({ statusCode: config.get.success });
60
61
  setTimeout(() => {
61
- process.exit(EXIT_NORMAL);
62
+ process.exit(EXIT_OK);
62
63
  }, TIMEOUT);
63
64
  });
64
65
 
@@ -1,7 +1,8 @@
1
+ /* eslint-disable max-lines-per-function */
1
2
  import './testEnv.js';
2
3
  import { afterEach, before, beforeEach, describe, it } from 'mocha';
3
4
  import { assert, spy } from 'sinon';
4
- import { expect, should } from 'chai';
5
+ import { expect } from 'chai';
5
6
  import { getCorrelationId } from '@mimik/request-helper';
6
7
  import { listen } from './retryMock.js';
7
8
  import logger from '@mimik/sumologic-winston-logger';
@@ -9,15 +10,19 @@ import { rpRetry } from '../index.js';
9
10
 
10
11
  const TIMEOUT = 50000;
11
12
  const RETRY_DELAY = 10000;
13
+ const OUT_OF_SCOPE_RETRY_DELAY = 100000;
14
+ const IN_SCOPE_RETRY_DELAY = 20000;
12
15
  const RETRY_TIMEOUT = 10;
16
+ const OUT_OF_SCOPE_RETRY_TIMEOUT = 150;
17
+ const IN_SCOPE_RETRY_TIMEOUT = 50;
13
18
  const RETRY_RETRIES = 2;
19
+ const OUT_OF_SCOPE_RETRIES = 20;
20
+ const IN_SCOPE_RETRIES = 10;
14
21
  const DELAY_STRATEGY_RESPONSE_1 = -20;
15
22
  const DELAY_STRATEGY_RESPONSE_2 = 50;
16
23
  const DELAY_STRATEGY_RESPONSE_3 = 100;
17
24
  const CALL_ARG_MESSAGE = 0;
18
25
  const FIRST_CALL = 0;
19
-
20
- should();
21
26
  /*
22
27
  * const DEFAULT_NO_MESSAGE = 'no message';
23
28
  * const NO_ERROR = 'not an error object';
@@ -69,7 +74,7 @@ describe('request-retry Unit Tests', () => {
69
74
  });
70
75
  });
71
76
  it('should generate warning: out of scope retries: 20 becoming max', () => {
72
- options.retry.retries = 20;
77
+ options.retry.retries = OUT_OF_SCOPE_RETRIES;
73
78
  return rpRetry(options).then(() => {
74
79
  assert.calledOnce(loggerSpyWarn);
75
80
  const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
@@ -78,7 +83,7 @@ describe('request-retry Unit Tests', () => {
78
83
  });
79
84
  });
80
85
  it('should not generate warning: in scope retries', () => {
81
- options.retry.retries = 10;
86
+ options.retry.retries = IN_SCOPE_RETRIES;
82
87
  return rpRetry(options).then(() => {
83
88
  assert.notCalled(loggerSpyWarn);
84
89
  });
@@ -102,7 +107,7 @@ describe('request-retry Unit Tests', () => {
102
107
  });
103
108
  });
104
109
  it('should generate warning: out of scope delay: 100000 becoming max', () => {
105
- options.retry.delay = 100000;
110
+ options.retry.delay = OUT_OF_SCOPE_RETRY_DELAY;
106
111
  return rpRetry(options).then(() => {
107
112
  assert.calledOnce(loggerSpyWarn);
108
113
  const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
@@ -120,7 +125,7 @@ describe('request-retry Unit Tests', () => {
120
125
  });
121
126
  });
122
127
  it('should not generate warning: in scope delay', () => {
123
- options.retry.delay = 20000;
128
+ options.retry.delay = IN_SCOPE_RETRY_DELAY;
124
129
  return rpRetry(options).then(() => {
125
130
  assert.notCalled(loggerSpyWarn);
126
131
  });
@@ -135,7 +140,7 @@ describe('request-retry Unit Tests', () => {
135
140
  });
136
141
  });
137
142
  it('should generate warning: out of scope timeout: 150 becoming max', () => {
138
- options.retry.timeout = 150;
143
+ options.retry.timeout = OUT_OF_SCOPE_RETRY_TIMEOUT;
139
144
  return rpRetry(options).then(() => {
140
145
  assert.calledOnce(loggerSpyWarn);
141
146
  const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
@@ -153,7 +158,7 @@ describe('request-retry Unit Tests', () => {
153
158
  });
154
159
  });
155
160
  it('should not generate warning: in scope timeout', () => {
156
- options.retry.timeout = 50;
161
+ options.retry.timeout = IN_SCOPE_RETRY_TIMEOUT;
157
162
  return rpRetry(options).then(() => {
158
163
  assert.notCalled(loggerSpyWarn);
159
164
  });
@@ -269,7 +274,7 @@ describe('request-retry Unit Tests', () => {
269
274
  options.retry.retryStrategy = () => {
270
275
  throw new Error('this is a test');
271
276
  };
272
- options.retry.retries = 2;
277
+ options.retry.retries = RETRY_RETRIES;
273
278
  return rpRetry(options)
274
279
  .catch(() => {
275
280
  assert.called(loggerSpyWarn);
@@ -280,7 +285,7 @@ describe('request-retry Unit Tests', () => {
280
285
  });
281
286
  it('should generate warning: bad retry strategy (not returning a boolean), ignoring and using default retries', () => {
282
287
  options.retry.retryStrategy = () => 'notABoolean';
283
- options.retry.retries = 2;
288
+ options.retry.retries = RETRY_RETRIES;
284
289
  return rpRetry(options)
285
290
  .catch(() => {
286
291
  assert.called(loggerSpyWarn);
@@ -291,7 +296,7 @@ describe('request-retry Unit Tests', () => {
291
296
  });
292
297
  it('should not generate warning: retry strategy returning a boolean', () => {
293
298
  options.retry.retryStrategy = () => false;
294
- options.retry.retries = 2;
299
+ options.retry.retries = RETRY_RETRIES;
295
300
  return rpRetry(options)
296
301
  .catch(() => {
297
302
  assert.called(loggerSpyWarn);
@@ -301,7 +306,7 @@ describe('request-retry Unit Tests', () => {
301
306
  options.retry.delayStrategy = () => {
302
307
  throw new Error('this is a test');
303
308
  };
304
- options.retry.retries = 2;
309
+ options.retry.retries = RETRY_RETRIES;
305
310
  return rpRetry(options)
306
311
  .catch(() => {
307
312
  assert.called(loggerSpyWarn);
@@ -312,7 +317,7 @@ describe('request-retry Unit Tests', () => {
312
317
  });
313
318
  it('should generate warning: bad delay strategy (not returning a number), ignoring and using default delay', () => {
314
319
  options.retry.delayStrategy = () => 'notANumber';
315
- options.retry.retries = 2;
320
+ options.retry.retries = RETRY_RETRIES;
316
321
  return rpRetry(options)
317
322
  .catch(() => {
318
323
  assert.called(loggerSpyWarn);
@@ -323,7 +328,7 @@ describe('request-retry Unit Tests', () => {
323
328
  });
324
329
  it('should generate warning: bad delay strategy (returning an out of scope number), ignoring and using default delay', () => {
325
330
  options.retry.delayStrategy = () => DELAY_STRATEGY_RESPONSE_1;
326
- options.retry.retries = 2;
331
+ options.retry.retries = RETRY_RETRIES;
327
332
  return rpRetry(options)
328
333
  .catch(() => {
329
334
  assert.called(loggerSpyWarn);
@@ -334,7 +339,7 @@ describe('request-retry Unit Tests', () => {
334
339
  });
335
340
  it('should not generate warning: delay strategy returning an in scope number', () => {
336
341
  options.retry.delayStrategy = () => DELAY_STRATEGY_RESPONSE_2;
337
- options.retry.retries = 2;
342
+ options.retry.retries = RETRY_RETRIES;
338
343
  return rpRetry(options)
339
344
  .catch(() => {
340
345
  assert.called(loggerSpyWarn);
package/test/testEnv.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint no-process-env: "off" */
2
- import process from 'process';
2
+ import process from 'node:process';
3
3
 
4
4
  /**
5
5
  * The following environment variables are set for the test: