@autofleet/network 1.5.2 → 1.6.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.
Files changed (2) hide show
  1. package/index.js +97 -58
  2. package/package.json +14 -12
package/index.js CHANGED
@@ -1,22 +1,27 @@
1
- const axios = require('axios');
2
- const axiosRetry = require('axios-retry');
3
- const Logger = require('@autofleet/logger');
4
- const qs = require('qs');
5
- const httpAdapter = require('axios/lib/adapters/http');
1
+ const { default: axios } = require('axios');
2
+ const { default: axiosRetry } = require('axios-retry');
3
+ const { default: Logger } = require('@autofleet/logger');
6
4
  const merge = require('deepmerge');
7
5
  const { setup } = require('@autofleet/axios-cache-adapter');
8
- const Agent = require('agentkeepalive');
9
- const HttpsAgent = require('agentkeepalive').HttpsAgent;
6
+ const { HttpAgent, HttpsAgent } = require('agentkeepalive');
10
7
 
11
- require('dotenv').config();
12
8
  /**
13
9
  * Add support for nock testing
14
- * see s://github.com/axios/axios/issues/305
10
+ * see https://github.com/axios/axios/issues/305
15
11
  */
16
12
  if (process.env.NODE_ENV === 'test') {
13
+ require('dotenv').config();
14
+ const httpAdapter = require('axios/lib/adapters/http');
17
15
  axios.defaults.adapter = httpAdapter;
18
16
  }
19
17
 
18
+ /** @type {import('qs').stringify | undefined} */
19
+ let qsStringify;
20
+ const lazilyLoadQsStringify = () => {
21
+ qsStringify ??= require('qs').stringify;
22
+ return qsStringify;
23
+ }
24
+
20
25
  const HTTPMethods = [
21
26
  'get',
22
27
  'post',
@@ -27,20 +32,33 @@ const HTTPMethods = [
27
32
  'options',
28
33
  ];
29
34
 
35
+ /** @type {NonNullable<Parameters<import('qs').stringify>[1]>} */
36
+ const qsStringifyOptions = { arrayFormat: 'brackets' };
37
+
30
38
  const defaultSettings = {
31
- timeout: 10000,
39
+ timeout: 10_000,
32
40
  headers: {
33
41
  'X-AF-AUTH': 'ANYONE',
34
42
  'X-IAF-ORIGIN-SERVICE': process.env.AF_SERVICE_NAME || null,
35
43
  },
36
- paramsSerializer: params => qs.stringify(params, { arrayFormat: 'brackets' }),
44
+ paramsSerializer: params => lazilyLoadQsStringify(params, qsStringifyOptions),
37
45
  'axios-retry': {
38
46
  shouldResetTimeout: true,
39
47
  },
40
48
  keepAlive: true,
41
49
  };
42
50
 
43
- const createRequestString = (request) => `[${(request.method || '').toUpperCase()}] ${request.baseURL}${request.url}`;
51
+ /** @param {import('axios').AxiosRequestConfig} request @returns {string} */
52
+ const createRequestString = (request) => `[${(request.method || '').toUpperCase()}] ${request.baseURL ?? 'unknown-base-url'}${request.url ?? '/unknown-url'}`;
53
+
54
+ /**
55
+ * @typedef {object} NetworkSettingsBase
56
+ * @property {string} [serviceName]
57
+ * @property {string} [serviceUrl]
58
+ * @property {boolean} [keepAlive]
59
+ * @property {import('@autofleet/logger').LoggerInstanceManager} [logger]
60
+ * @typedef {import('axios').CreateAxiosDefaults & NetworkSettingsBase} NetworkSettings
61
+ */
44
62
 
45
63
  /*
46
64
  The default free socket keepalive timeout, in milliseconds.
@@ -48,45 +66,59 @@ const createRequestString = (request) => `[${(request.method || '').toUpperCase(
48
66
  and we want to make sure we don't have any socket issues.
49
67
  See https://www.npmjs.com/package/agentkeepalive
50
68
 
51
- There is also autoscaling reason for that, if we dont timeout at some point,
69
+ There is also autoscaling reason for that, if we don't timeout at some point,
52
70
  new pods that were auto scaled will not be used.
53
71
  */
54
- const FREE_SOCKET_TIMEOUT = process.env.FREE_SOCKET_TIMEOUT || 5000;
72
+ const FREE_SOCKET_TIMEOUT = process.env.FREE_SOCKET_TIMEOUT || 5_000;
55
73
 
56
74
  module.exports = class Network {
75
+ /** @private @readonly @type {NetworkSettings} */
76
+ settings;
77
+ /** @private @readonly @type {import('axios').AxiosInstance} */
78
+ axios;
79
+
80
+ /** @type {import('axios').AxiosInstance['get']} */
81
+ get;
82
+ /** @type {import('axios').AxiosInstance['post']} */
83
+ post;
84
+ /** @type {import('axios').AxiosInstance['delete']} */
85
+ delete;
86
+ /** @type {import('axios').AxiosInstance['head']} */
87
+ head;
88
+ /** @type {import('axios').AxiosInstance['put']} */
89
+ put;
90
+ /** @type {import('axios').AxiosInstance['patch']} */
91
+ patch;
92
+ /** @type {import('axios').AxiosInstance['options']} */
93
+ options;
94
+
95
+ /** @param {NetworkSettings} [settings] */
57
96
  constructor(settings = {}) {
58
97
  this.settings = merge(defaultSettings, settings);
59
98
  if (this.settings.keepAlive) {
60
- const keepaliveAgent = new Agent({
61
- freeSocketTimeout: FREE_SOCKET_TIMEOUT,
62
- });
63
- const keepaliveHttpsAgent = new HttpsAgent({
64
- freeSocketTimeout: FREE_SOCKET_TIMEOUT,
65
- });
66
- this.settings.httpAgent = keepaliveAgent;
67
- this.settings.httpsAgent = keepaliveHttpsAgent;
99
+ this.settings.httpAgent = new HttpAgent({ freeSocketTimeout: FREE_SOCKET_TIMEOUT });
100
+ this.settings.httpsAgent = new HttpsAgent({ freeSocketTimeout: FREE_SOCKET_TIMEOUT });
68
101
  delete this.settings.keepAlive;
69
102
  }
70
- this.createBaseUrl();
103
+ this.#createBaseUrl();
71
104
 
72
105
  if(settings.cache) {
73
106
  this.settings.cache = {
74
107
  maxAge: 15 * 60 * 1000,
75
- exclude: {
76
- // Store responses from requests with query parameters in cache
77
- query: false
78
- },
108
+ // Store responses from requests with query parameters in cache
109
+ exclude: { query: false },
79
110
  ...settings.cache,
80
111
  }
81
112
  }
82
113
 
83
114
  this.axios = settings.cache ? setup(this.settings) : axios.create(this.settings);
84
- this.addRetry(settings);
85
- this.buildClassHttpMethods();
86
- this.addLogs();
115
+ this.#addRetry(settings);
116
+ this.#buildClassHttpMethods();
117
+ this.#addLogs();
87
118
  }
88
119
 
89
- addRetry(settings) {
120
+ /** @param {NetworkSettings} settings */
121
+ #addRetry(settings) {
90
122
  axiosRetry(this.axios, {
91
123
  retries: 0,
92
124
  retryDelay: axiosRetry.exponentialDelay,
@@ -94,8 +126,8 @@ module.exports = class Network {
94
126
  });
95
127
  }
96
128
 
97
- addLogs() {
98
- const logger = Logger();
129
+ #addLogs() {
130
+ const logger = this.settings.logger ?? Logger();
99
131
  this.axios.interceptors.request.use((request) => {
100
132
  logger.info(`Start Request: ${createRequestString(request)}`);
101
133
  return request;
@@ -105,10 +137,7 @@ module.exports = class Network {
105
137
  logger.info(`Finish Request: ${createRequestString(response.config)}`);
106
138
  return response;
107
139
  }, (error) => {
108
- if(error.request && error.request._currentRequest &&
109
- error.request._currentRequest.reusedSocket &&
110
- ['ECONNRESET', 'EPIPE'].includes(error.code)
111
- ) {
140
+ if(error.request?._currentRequest?.reusedSocket && ['ECONNRESET', 'EPIPE'].includes(error.code)) {
112
141
  // See https://www.npmjs.com/package/agentkeepalive
113
142
  // Support req.reusedSocket
114
143
  // https://code-examples.net/en/q/28a8069
@@ -118,16 +147,16 @@ module.exports = class Network {
118
147
  })
119
148
  return this.axios.request(error.config);
120
149
  }
121
- const request = error.request;
150
+ const request = error.config?.baseURL ? error.config : error.request;
122
151
  logger.error(`Finish Request with error ${request ? createRequestString(request) : ''}`, {
123
152
  status: error.status,
124
- data: error.response && error.response.data
153
+ data: error.response?.data,
125
154
  });
126
155
  throw error;
127
156
  });
128
157
  }
129
158
 
130
- createBaseUrl() {
159
+ #createBaseUrl() {
131
160
  if (!this.settings.serviceUrl && !this.settings.serviceName) {
132
161
  throw new Error('At least one of the settings Missing serviceUrl or serviceName');
133
162
  }
@@ -147,44 +176,54 @@ module.exports = class Network {
147
176
  /**
148
177
  * Build class methods that wrap axios methods
149
178
  */
150
- buildClassHttpMethods() {
179
+ #buildClassHttpMethods() {
151
180
  HTTPMethods.forEach((method) => {
152
181
  this[method] = (...args) => this.axios[method](...args);
153
182
  });
154
183
  }
155
184
 
185
+ /**
186
+ * @template T
187
+ * @param {string} url - The endpoint URL to send the request to.
188
+ * @param {object} [options={}] - Additional options to include in the request payload.
189
+ * @returns {Promise<T[]>} - A promise that resolves to an array of all results.
190
+ */
156
191
  async getAllPages(url, options = {}) {
192
+ /** @type {T[]} */
157
193
  let currentResult = [];
194
+ /** @type {number | null} */
158
195
  let resultsInFirstPage = null;
196
+ /** @type {number | null} */
159
197
  let lastResultsSize = null;
160
198
 
161
- const localOptions = { params: {}, ...options };
162
- localOptions.params.page = 1;
163
- while((localOptions.params.page === 1 || lastResultsSize === resultsInFirstPage) && lastResultsSize !== 0) {
199
+ const localOptions = { params: { page: 1 }, ...options };
200
+ while ((localOptions.params.page === 1 || lastResultsSize === resultsInFirstPage) && lastResultsSize !== 0) {
164
201
  const { data } = await this.get(url, localOptions);
165
202
  lastResultsSize = data.length;
166
- if(localOptions.params.page === 1) {
203
+ if (localOptions.params.page === 1) {
167
204
  resultsInFirstPage = data.length;
168
205
  }
169
206
 
170
207
  localOptions.params.page += 1;
171
- currentResult = currentResult.concat(data);
208
+ Array.prototype.push.apply(currentResult, data);
172
209
  }
173
210
 
174
211
  return currentResult;
175
212
  }
176
213
 
177
- /**
178
- * Fetches all pages from a paginated API endpoint until all results are retrieved
179
- * or an optional page limit is reached.
180
- *
181
- * @param {string} url - The endpoint URL to send the request to.
182
- * @param {object} [options={}] - Additional options to include in the request payload.
183
- * @param {number} [pageLimit=100] - The maximum number of pages to fetch.
184
- * Set to -1 for no limit.
185
- * @returns {Promise<Array>} - A promise that resolves to an array of all results.
186
- */
214
+ /**
215
+ * Fetches all pages from a paginated API endpoint until all results are retrieved
216
+ * or an optional page limit is reached.
217
+ *
218
+ * @template T
219
+ * @param {string} url - The endpoint URL to send the request to.
220
+ * @param {object} [options={}] - Additional options to include in the request payload.
221
+ * @param {number} [pageLimit=100] - The maximum number of pages to fetch.
222
+ * Set to -1 for no limit.
223
+ * @returns {Promise<T[]>} - A promise that resolves to an array of all results.
224
+ */
187
225
  async getAllPagesFromQueryEndpoint(url, options = {}, pageLimit = 100) {
226
+ /** @type {T[]} */
188
227
  let currentResult = [];
189
228
 
190
229
  let moreResultsToLoad = true;
@@ -192,11 +231,11 @@ module.exports = class Network {
192
231
  while (moreResultsToLoad && (pageLimit === -1 || page < pageLimit)) {
193
232
  const { data } = await this.post(url, {
194
233
  ...options,
195
- page: page,
234
+ page,
196
235
  });
197
236
  const { rows, count } = data;
198
237
  page += 1;
199
- currentResult = currentResult.concat(rows);
238
+ Array.prototype.push.apply(currentResult, rows);
200
239
  moreResultsToLoad = currentResult.length < count;
201
240
  }
202
241
 
package/package.json CHANGED
@@ -1,27 +1,29 @@
1
1
  {
2
2
  "name": "@autofleet/network",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "jest --forceExit --runInBand",
8
- "test-auto": "jest --watch --runInBand",
9
- "linter": "./node_modules/.bin/eslint ."
7
+ "test": "node --test"
10
8
  },
11
9
  "author": "Dor Shay",
12
10
  "license": "ISC",
13
11
  "dependencies": {
14
12
  "@autofleet/axios-cache-adapter": "^2.7.3-hotfix-1",
15
- "@autofleet/logger": "^2.0.2",
16
- "agentkeepalive": "^4.3.0",
17
- "axios": "^0.26.1",
18
- "axios-retry": "^3.2.4",
13
+ "agentkeepalive": "^4.6.0",
14
+ "axios": "^0.29.0",
15
+ "axios-retry": "^4.5.0",
19
16
  "deepmerge": "^3.0.0",
20
- "dotenv": "^6.0.0",
21
- "qs": "^6.5.2"
17
+ "qs": "^6.14.0"
18
+ },
19
+ "peerDependencies": {
20
+ "@autofleet/logger": ">=4.0.0"
22
21
  },
23
22
  "devDependencies": {
24
- "jest": "^27.0.6",
25
- "nock": "^10.0.0"
23
+ "@autofleet/logger": "^4.1.0",
24
+ "@types/axios": "^0.9.36",
25
+ "@types/node": "^18.19.75",
26
+ "dotenv": "^16.4.7",
27
+ "nock": "^14.0.1"
26
28
  }
27
29
  }