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