@autofleet/network 1.5.2-beta-1 → 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.
- package/index.js +99 -50
- 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
|
|
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
|
|
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:
|
|
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 =>
|
|
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
|
-
|
|
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
|
|
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 ||
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
103
|
+
this.#createBaseUrl();
|
|
71
104
|
|
|
72
105
|
if(settings.cache) {
|
|
73
106
|
this.settings.cache = {
|
|
74
107
|
maxAge: 15 * 60 * 1000,
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
85
|
-
this
|
|
86
|
-
this
|
|
115
|
+
this.#addRetry(settings);
|
|
116
|
+
this.#buildClassHttpMethods();
|
|
117
|
+
this.#addLogs();
|
|
87
118
|
}
|
|
88
119
|
|
|
89
|
-
|
|
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.
|
|
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
|
|
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,46 +176,66 @@ 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
|
|
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
|
|
208
|
+
Array.prototype.push.apply(currentResult, data);
|
|
172
209
|
}
|
|
173
210
|
|
|
174
211
|
return currentResult;
|
|
175
212
|
}
|
|
176
213
|
|
|
177
|
-
|
|
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
|
+
*/
|
|
225
|
+
async getAllPagesFromQueryEndpoint(url, options = {}, pageLimit = 100) {
|
|
226
|
+
/** @type {T[]} */
|
|
178
227
|
let currentResult = [];
|
|
179
228
|
|
|
180
229
|
let moreResultsToLoad = true;
|
|
181
230
|
let page = 1;
|
|
182
|
-
while(moreResultsToLoad && page <
|
|
231
|
+
while (moreResultsToLoad && (pageLimit === -1 || page < pageLimit)) {
|
|
183
232
|
const { data } = await this.post(url, {
|
|
184
233
|
...options,
|
|
185
|
-
page
|
|
234
|
+
page,
|
|
186
235
|
});
|
|
187
236
|
const { rows, count } = data;
|
|
188
237
|
page += 1;
|
|
189
|
-
currentResult
|
|
238
|
+
Array.prototype.push.apply(currentResult, rows);
|
|
190
239
|
moreResultsToLoad = currentResult.length < count;
|
|
191
240
|
}
|
|
192
241
|
|
package/package.json
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/network",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
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
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"axios": "^
|
|
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
|
-
"
|
|
21
|
-
|
|
17
|
+
"qs": "^6.14.0"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@autofleet/logger": ">=4.0.0"
|
|
22
21
|
},
|
|
23
22
|
"devDependencies": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
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
|
}
|