rqlite-js 2.5.7 → 3.0.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 (71) hide show
  1. package/README.md +354 -157
  2. package/dist/index.cjs +904 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +329 -0
  5. package/dist/index.d.ts +329 -0
  6. package/dist/index.js +865 -0
  7. package/dist/index.js.map +1 -0
  8. package/docs/MIGRATION-v2-to-v3.md +71 -0
  9. package/package.json +73 -42
  10. package/src/api/backup/index.ts +69 -0
  11. package/src/api/backup-formats.ts +8 -0
  12. package/src/api/client/index.ts +97 -0
  13. package/src/api/consistency-levels.ts +10 -0
  14. package/src/api/data/index.ts +87 -0
  15. package/src/api/paths.ts +13 -0
  16. package/src/api/query-params.ts +15 -0
  17. package/src/api/results/data-result-error.ts +41 -0
  18. package/src/api/results/data-result.ts +91 -0
  19. package/src/api/results/data-results.ts +91 -0
  20. package/src/api/results/index.ts +7 -0
  21. package/src/api/results/result-fields.ts +14 -0
  22. package/src/api/status/index.ts +53 -0
  23. package/src/http-request/auth-keys.ts +14 -0
  24. package/{es6/http-request/errors.js → src/http-request/errors.ts} +4 -3
  25. package/src/http-request/http-headers.ts +19 -0
  26. package/{es6/http-request/http-methods.js → src/http-request/http-methods.ts} +0 -27
  27. package/src/http-request/index.ts +676 -0
  28. package/{es6/http-request/retryable.js → src/http-request/retryable.ts} +1 -1
  29. package/src/index.ts +38 -0
  30. package/.eslintrc +0 -20
  31. package/.travis.yml +0 -24
  32. package/es6/api/backup/index.js +0 -71
  33. package/es6/api/client/index.js +0 -83
  34. package/es6/api/data/index.js +0 -129
  35. package/es6/api/results/data-result-error.js +0 -32
  36. package/es6/api/results/data-result.js +0 -134
  37. package/es6/api/results/data-results.js +0 -121
  38. package/es6/api/results/index.js +0 -7
  39. package/es6/api/status/index.js +0 -54
  40. package/es6/http-request/index.js +0 -743
  41. package/es6/index.js +0 -19
  42. package/jsconfig.json +0 -19
  43. package/lib/api/backup/index.js +0 -72
  44. package/lib/api/backup/index.js.map +0 -1
  45. package/lib/api/client/index.js +0 -84
  46. package/lib/api/client/index.js.map +0 -1
  47. package/lib/api/data/index.js +0 -130
  48. package/lib/api/data/index.js.map +0 -1
  49. package/lib/api/results/data-result-error.js +0 -33
  50. package/lib/api/results/data-result-error.js.map +0 -1
  51. package/lib/api/results/data-result.js +0 -135
  52. package/lib/api/results/data-result.js.map +0 -1
  53. package/lib/api/results/data-results.js +0 -122
  54. package/lib/api/results/data-results.js.map +0 -1
  55. package/lib/api/results/index.js +0 -8
  56. package/lib/api/results/index.js.map +0 -1
  57. package/lib/api/status/index.js +0 -55
  58. package/lib/api/status/index.js.map +0 -1
  59. package/lib/http-request/content-types.js +0 -5
  60. package/lib/http-request/content-types.js.map +0 -1
  61. package/lib/http-request/errors.js +0 -17
  62. package/lib/http-request/errors.js.map +0 -1
  63. package/lib/http-request/http-methods.js +0 -40
  64. package/lib/http-request/http-methods.js.map +0 -1
  65. package/lib/http-request/index.js +0 -744
  66. package/lib/http-request/index.js.map +0 -1
  67. package/lib/http-request/retryable.js +0 -42
  68. package/lib/http-request/retryable.js.map +0 -1
  69. package/lib/index.js +0 -20
  70. package/lib/index.js.map +0 -1
  71. /package/{es6/http-request/content-types.js → src/http-request/content-types.ts} +0 -0
@@ -1,743 +0,0 @@
1
- /**
2
- * Plain HTTP client to be used when creating RQLite specific API HTTP clients
3
- * @module http-request
4
- */
5
- import axios from 'axios'
6
- import { stringify as stringifyQuery } from 'qs'
7
- import { parse as parseUrl } from 'url'
8
- import {
9
- HTTP_METHOD_GET,
10
- HTTP_METHOD_POST,
11
- } from './http-methods'
12
- import {
13
- CONTENT_TYPE_APPLICATION_JSON,
14
- // CONTENT_TYPE_APPLICATION_OCTET_STREAM,
15
- } from './content-types'
16
- import { ERROR_HTTP_REQUEST_MAX_REDIRECTS } from './errors'
17
- import {
18
- RETRYABLE_ERROR_CODES,
19
- RETRYABLE_HTTP_METHODS,
20
- RETRYABLE_STATUS_CODES,
21
- } from './retryable'
22
-
23
- /**
24
- * RQliteJS HTTP Request options
25
- * @typedef HttpRequestOptions
26
- * @type {Object}
27
- * @property {Object} [auth] A object for user authentication
28
- * @property {String} [auth.username] The username for authentication
29
- * @property {String} [auth.password] The username for authentication
30
- * @property {Object} [body] The body of the HTTP request
31
- * @property {Object} [headers={}] HTTP headers to send with the request
32
- * @property {String} [httpMethod=HTTP_METHOD_GET] The HTTP method for the request
33
- * i.e. GET or POST
34
- * @property {Object} [query] An object with the query to send with the HTTP request
35
- * @property {Boolean} [stream=false] When true the returned value is a request object with
36
- * stream support instead of a request-promise result
37
- * @property {Number} [timeout=DEAULT_TIMEOUT] Optional timeout to override default
38
- * @property {String} uri The uri for the request which can be a relative path to use
39
- * the currently active host or a full i.e. http://localhost:4001/db/query which is used
40
- * literally
41
- * @property {Boolean} [useLeader=false] When true the request will use the master host, the
42
- * first host in this.hosts, this is ideal for write operations to skip the redirect
43
- * @property {Number} [retries] The number of retries, defaults to the number of
44
- * hosts times 3
45
- * @property {Number} [maxRedirects=10] The maximum number of HTTP redirects to follow before
46
- * throwing an error
47
- * @property {Number} [attempt=0] The current attempt count when retrying or redirecting
48
- * @property {Number} [retryAttempt=0] The current attempt based on retry logic
49
- * @property {Number} [redirectAttempt=0] The current attempt based on redirect logic
50
- * @property {Number} [attemptHostIndex] When in a retry state the host index of the last
51
- * attempt which is used to get the next host index
52
- * @property {import('http').Agent} [httpAgent] An option http agent, useful for
53
- * keepalive pools using plain HTTP
54
- * @property {import('https').Agent} [httpsAgent] An option http agent, useful
55
- * for keepalive pools using SSL
56
- */
57
-
58
- /**
59
- * The default timeout value
60
- */
61
- export const DEAULT_TIMEOUT = 30000
62
-
63
- /**
64
- * The default to retry a request using the next host in the chain
65
- */
66
- export const DEAULT_RETRY_DELAY = 30000
67
-
68
- /**
69
- * Create default header for all HTTP requests
70
- * @param {Object} [headers={}] HTTP headers to send with the request
71
- * @returns {Object} The headers with defaults applied
72
- */
73
- export function createDefaultHeaders (headers = {}) {
74
- const { Accept = CONTENT_TYPE_APPLICATION_JSON } = headers
75
- return { ...headers, Accept }
76
- }
77
-
78
- /**
79
- * Clean the request path remove / from the beginning
80
- * @param {String} path The path to clean
81
- * @returns {String} The clean path
82
- */
83
- function cleanPath (path) {
84
- return String(path).replace(/^\//, '')
85
- }
86
-
87
- /**
88
- * Returns the next wait interval, in milliseconds, using an exponential
89
- * backoff algorithm.
90
- * @param {Number} attempt The retry attempt
91
- * @param {Number} base The base of the exponential backoff
92
- * @param {Number} exponent The exponential power
93
- * @returns {Number} The time to wait in milliseconds
94
- */
95
- export function getWaitTimeExponential (attempt = 0, base = 100, exponent = 2) {
96
- if (attempt === 0) {
97
- return 0
98
- }
99
- return (exponent ** attempt) * base
100
- }
101
-
102
- /**
103
- * Generic HTTP Request class which all RQLiteJS client
104
- * should extend for consistent communitication with an RQLite
105
- * server
106
- */
107
- export default class HttpRequest {
108
- /**
109
- * The index of the host in this.hosts which will be tried
110
- * first before attempting other hosts
111
- * @type {Number}
112
- */
113
- activeHostIndex = 0
114
-
115
- /**
116
- * Whether or not the setNextActiveHostIndex() should
117
- * perform a round robin strategy
118
- */
119
- activeHostRoundRobin = true
120
-
121
- /**
122
- * The regex pattern to check if a uri is absolute or relative,
123
- * if it is absolute the host is not appended
124
- */
125
- absoluteUriPattern = /^https?:\/\//
126
-
127
- /**
128
- * A list of hosts that are tried in round robin fashion
129
- * when certain HTTP responses are received
130
- * @type {String[]}
131
- */
132
- hosts = []
133
-
134
- /**
135
- * @type {import('http').Agent} The http agent if it is set
136
- */
137
- httpAgent
138
-
139
- /**
140
- * @type {import('https').Agent} The https agent if it is set
141
- */
142
- httpsAgent
143
-
144
- /**
145
- * The host list index of the leader node defaults
146
- * to the first host
147
- * @type {Number}
148
- */
149
- leaderHostIndex = 0
150
-
151
- /**
152
- * Http error codes which are considered retryable
153
- * @type {Set}
154
- */
155
- retryableErrorCodes = RETRYABLE_ERROR_CODES
156
-
157
- /**
158
- * Http status codes which are considered retryable
159
- * @type {Set}
160
- */
161
- retryableStatusCodes = RETRYABLE_STATUS_CODES
162
-
163
- /**
164
- * Http methods which are considered retryable
165
- * @type {Set}
166
- */
167
- retryableHttpMethods = RETRYABLE_HTTP_METHODS
168
-
169
- /**
170
- * The exponential backoff base for retries
171
- */
172
- exponentailBackoffBase = 100
173
-
174
- /**
175
- * The exponent to increase backoff each try
176
- */
177
- exponentailBackoffExponent = 1.1
178
-
179
- /**
180
- * Authentication Map
181
- * @type {Map}
182
- * @property {String} username
183
- * @property {String} password
184
- */
185
- authentication = new Map()
186
-
187
- /**
188
- * Construtor for HttpRequest
189
- * @param {String[]|String} hosts An array of RQLite hosts or a string
190
- * that will be split on "," to create an array of hosts, the first
191
- * host will be tried first when there are multiple hosts
192
- * @param {Object} [options={}] Additional options
193
- * @param {Object} [options.authentication] Authentication options
194
- * @param {String} [options.authentication.username] The host authentication username
195
- * @param {String} [options.authentication.password] The host authentication password
196
- * @param {Boolean} [options.activeHostRoundRobin=true] If true this.setNextActiveHostIndex()
197
- * will perform a round robin when called
198
- * @param {import('http').Agent} [options.httpAgent] An option http agent, useful for
199
- * keepalive pools using plain HTTP
200
- * @param {import('https').Agent} [options.httpsAgent] An option http agent, useful
201
- * for keepalive pools using SSL
202
- * @param {Set|String[]} [options.retryableErrorCodes] The list of retryable error codes
203
- * @param {Set|Number[]} [options.retryableStatusCodes] The list of retryable http status codes
204
- * @param {Set|String[]} [options.retryableHttpMethods] The list of retryable http methods
205
- * @param {Number} [options.exponentailBackoffBase] The value for exponentail backoff base
206
- * for retry exponential backoff
207
- * @param {Number} [options.exponentailBackoffExponent] Exponent to increase backoff each try
208
- */
209
- constructor (hosts, options = {}) {
210
- this.setHosts(hosts)
211
- if (this.getTotalHosts() === 0) {
212
- throw new Error('At least one host must be provided')
213
- }
214
- const {
215
- activeHostRoundRobin = true,
216
- httpAgent,
217
- httpsAgent,
218
- retryableErrorCodes,
219
- retryableStatusCodes,
220
- retryableHttpMethods,
221
- exponentailBackoffExponent,
222
- exponentailBackoffBase,
223
- authentication,
224
- } = options
225
- if (typeof authentication === 'object') {
226
- const { username, password } = authentication
227
- if (username) {
228
- this.authentication.set('username', username)
229
- }
230
- if (password) {
231
- this.authentication.set('password', password)
232
- }
233
- }
234
- if (typeof activeHostRoundRobin !== 'undefined') {
235
- this.setActiveHostRoundRobin(activeHostRoundRobin)
236
- }
237
- if (typeof httpAgent !== 'undefined') {
238
- this.setHttpAgent(httpAgent)
239
- }
240
- if (typeof httpsAgent !== 'undefined') {
241
- this.setHttpsAgent(httpsAgent)
242
- }
243
- if (retryableErrorCodes instanceof Set || Array.isArray(retryableErrorCodes)) {
244
- this.setRetryableErrorCodes(
245
- Array.isArray(retryableErrorCodes) ? Set(retryableErrorCodes) : retryableErrorCodes,
246
- )
247
- }
248
- if (retryableStatusCodes instanceof Set || Array.isArray(retryableStatusCodes)) {
249
- this.setRetryableStatusCodes(
250
- Array.isArray(retryableStatusCodes) ? Set(retryableStatusCodes) : retryableStatusCodes,
251
- )
252
- }
253
- if (retryableHttpMethods instanceof Set || Array.isArray(retryableHttpMethods)) {
254
- this.setRetryableHttpMethods(
255
- Array.isArray(retryableHttpMethods) ? Set(retryableHttpMethods) : retryableHttpMethods,
256
- )
257
- }
258
- if (Number.isFinite(exponentailBackoffBase)) {
259
- this.setExponentailBackoffBase(exponentailBackoffBase)
260
- }
261
- if (Number.isFinite(exponentailBackoffExponent)) {
262
- this.setExponentailBackoffExponent(exponentailBackoffExponent)
263
- }
264
- }
265
-
266
- /**
267
- * Set authentication information
268
- * @param {Object} [authentication] Authentication data
269
- * @param {String} [authentication.username] The host authentication username
270
- * @param {String} [authentication.password] The host authentication password
271
- */
272
- setAuthentication (authentication = {}) {
273
- const { username, password } = authentication
274
- if (username) {
275
- this.authentication.set('username', username)
276
- }
277
- if (password) {
278
- this.authentication.set('password', password)
279
- }
280
- }
281
-
282
- /**
283
- * Set the exponentail backoff base
284
- * @param {Number} exponentailBackoffBase
285
- */
286
- setExponentailBackoffBase (exponentailBackoffBase) {
287
- this.exponentailBackoffBase = exponentailBackoffBase
288
- }
289
-
290
- /**
291
- * Get the exponentail backoff base
292
- * @return {Number} The exponentail backoff base
293
- */
294
- getExponentailBackoffBase () {
295
- return this.exponentailBackoffBase
296
- }
297
-
298
- /**
299
- * Set the exponentail backoff exponent
300
- * @param {Number} exponentailBackoffExponent
301
- */
302
- setExponentailBackoffExponent (exponentailBackoffExponent) {
303
- this.exponentailBackoffExponent = exponentailBackoffExponent
304
- }
305
-
306
- /**
307
- * Get the exponentail backoff exponent
308
- * @return {Number} The exponentail backoff exponent
309
- */
310
- getExponentailBackoffExponent () {
311
- return this.exponentailBackoffExponent
312
- }
313
-
314
- /**
315
- * Set the retryable error codes
316
- * @param {Set} retryableErrorCodes
317
- */
318
- setRetryableErrorCodes (retryableErrorCodes) {
319
- this.retryableErrorCodes = retryableErrorCodes
320
- }
321
-
322
- /**
323
- * Get the retryable error codes
324
- * @returns {Set}
325
- */
326
- getRetryableErrorCodes () {
327
- return this.retryableErrorCodes
328
- }
329
-
330
- /**
331
- * Set the retryable status codes
332
- * @param {Set} retryableStatusCodes
333
- */
334
- setRetryableStatusCodes (retryableStatusCodes) {
335
- this.retryableStatusCodes = retryableStatusCodes
336
- }
337
-
338
- /**
339
- * Get the retryable status codes
340
- * @returns {Set}
341
- */
342
- getRetryableStatusCodes () {
343
- return this.retryableStatusCodes
344
- }
345
-
346
- /**
347
- * Set the retryable http methods
348
- * @param {Set} retryableHttpMethods
349
- */
350
- setRetryableHttpMethods (retryableHttpMethods) {
351
- this.retryableHttpMethods = retryableHttpMethods
352
- }
353
-
354
- /**
355
- * Get the retryable http methods
356
- * @returns {Set}
357
- */
358
- getRetryableHttpMethods () {
359
- return this.retryableHttpMethods
360
- }
361
-
362
- /**
363
- * Set an http agent which is useful for http keepalive requests
364
- * @param {import('http').Agent} httpAgent An http agent
365
- */
366
- setHttpAgent (httpAgent) {
367
- this.httpAgent = httpAgent
368
- }
369
-
370
- /**
371
- * Get the set http agent
372
- * @returns {import('http').Agent|undefined} The https agent if it is set
373
- */
374
- getHttpAgent () {
375
- return this.httpAgent
376
- }
377
-
378
- /**
379
- * Set an https agent which is useful for https keepalive requests
380
- * @param {import('https').Agent} httpsAgent An https agent
381
- */
382
- setHttpsAgent (httpsAgent) {
383
- this.httpsAgent = httpsAgent
384
- }
385
-
386
- /**
387
- * Get the set https agent
388
- * @returns {import('https').Agent|undefined} The https agent if it is set
389
- */
390
- getHttpsAgent () {
391
- return this.httpsAgent
392
- }
393
-
394
- /**
395
- * Set the list of hosts
396
- * @param {String[]|String} hosts An array of RQLite hosts or a string
397
- * that will be split on "," to create an array of hosts, the first
398
- * host will be tried first when there are multiple hosts
399
- */
400
- setHosts (hosts) {
401
- this.hosts = !Array.isArray(hosts) ? String(hosts).split(',') : hosts
402
- this.hosts = this.hosts.reduce((acc, v) => {
403
- // Remove trailing slashed from hosts
404
- const host = String(v).trim().replace(/\/$/, '')
405
- if (!host) {
406
- return acc
407
- }
408
- return acc.concat(host)
409
- }, [])
410
- }
411
-
412
- /**
413
- * Get the list of hosts
414
- * @returns {String[]} The list of hosts
415
- */
416
- getHosts () {
417
- return this.hosts
418
- }
419
-
420
- /**
421
- * Given a host string find the index of that host in the hosts
422
- * @param {String} host A host to find in hosts
423
- * @returns {Number} The found host index or -1 if not found
424
- */
425
- findHostIndex (host) {
426
- const parsedHostToFind = parseUrl(host)
427
- return this.getHosts().findIndex((v) => {
428
- const parsedHost = parseUrl(v)
429
- // Find a host where all the parsed fields match the requested host
430
- return ['hostname', 'protocol', 'port', 'path'].every((field) => parsedHostToFind[field] === parsedHost[field])
431
- })
432
- }
433
-
434
- /**
435
- * Get the current active host from the hosts array
436
- * @param {Boolean} useLeader If true use the first host which is always
437
- * the master, this is prefered for write operations
438
- * @returns {String} The active host
439
- */
440
- getActiveHost (useLeader) {
441
- // When useLeader is true we should just use the first host
442
- const activeHostIndex = useLeader ? this.getLeaderHostIndex() : this.getActiveHostIndex()
443
- return this.getHosts()[activeHostIndex]
444
- }
445
-
446
- /**
447
- * Set the active host index with check based on this.hosts
448
- * @param {Number} activeHostIndex The index
449
- * @returns {Number} The active host index
450
- */
451
- setActiveHostIndex (activeHostIndex) {
452
- if (!Number.isFinite(activeHostIndex)) {
453
- throw new Error('The activeHostIndex should be a finite number')
454
- }
455
- const totalHosts = this.getTotalHosts()
456
- if (activeHostIndex < 0) {
457
- // Don't allow an index less then zero
458
- this.activeHostIndex = 0
459
- } else if (activeHostIndex >= totalHosts) {
460
- // Don't allow an index greater then the length of the hosts
461
- this.activeHostIndex = totalHosts - 1
462
- } else {
463
- this.activeHostIndex = activeHostIndex
464
- }
465
- return this.activeHostIndex
466
- }
467
-
468
- /**
469
- * Get the host index for the leader node
470
- * @returns {Number} The host index for the leader node
471
- */
472
- getLeaderHostIndex () {
473
- return this.leaderHostIndex
474
- }
475
-
476
- /**
477
- * Set the index in the host array for the leader node
478
- * @param {Number} leaderHostIndex The index of the host that is the leader node
479
- * @returns {Number} The host index for the leader node
480
- */
481
- setLeaderHostIndex (leaderHostIndex) {
482
- if (!Number.isFinite(leaderHostIndex)) {
483
- throw new Error('The leaderHostIndex should be a finite number')
484
- }
485
- const totalHosts = this.getTotalHosts()
486
- if (leaderHostIndex < 0) {
487
- this.leaderHostIndex = 0
488
- } else if (leaderHostIndex > totalHosts) {
489
- this.leaderHostIndex = totalHosts - 1
490
- } else {
491
- this.leaderHostIndex = leaderHostIndex
492
- }
493
- return this.leaderHostIndex
494
- }
495
-
496
- /**
497
- * Get the active host index
498
- * @returns {Number} The active host index
499
- */
500
- getActiveHostIndex () {
501
- return this.activeHostIndex
502
- }
503
-
504
- /**
505
- * Set active host round robin value
506
- * @param {Boolean} activeHostRoundRobin If true setActiveHostIndex() will
507
- * perform a round robin
508
- */
509
- setActiveHostRoundRobin (activeHostRoundRobin) {
510
- if (typeof activeHostRoundRobin !== 'boolean') {
511
- throw new Error('The activeHostRoundRobin argument must be boolean')
512
- }
513
- this.activeHostRoundRobin = activeHostRoundRobin
514
- }
515
-
516
- /**
517
- * Get active host round robin value
518
- * @returns {Boolean} The value of activeHostRoundRobin
519
- */
520
- getActiveHostRoundRobin () {
521
- return this.activeHostRoundRobin
522
- }
523
-
524
- /**
525
- * Get the next active host index
526
- * @param {Number} [activeHostIndex] An optional paramater to provide the active host index
527
- * @returns {Number} The next active host index which will wrap around to zero
528
- */
529
- getNextActiveHostIndex (activeHostIndex = this.getActiveHostIndex()) {
530
- const totalHosts = this.getTotalHosts()
531
- const nextIndex = activeHostIndex + 1
532
- // If we are past the last index start back over at 1
533
- if (totalHosts === nextIndex) {
534
- return 0
535
- }
536
- return nextIndex
537
- }
538
-
539
- /**
540
- * Set the active host index to the next host using a
541
- * round robin strategy
542
- */
543
- setNextActiveHostIndex () {
544
- // Don't bother if we only have one host
545
- if (!this.getActiveHostRoundRobin()) {
546
- return
547
- }
548
- const totalHosts = this.getTotalHosts()
549
- if (this.getActiveHostRoundRobin() && totalHosts <= 1) {
550
- return
551
- }
552
- this.setActiveHostIndex(this.getNextActiveHostIndex())
553
- }
554
-
555
- /**
556
- * Get the total number of hosts
557
- * @returns {Number} The total number of hosts
558
- */
559
- getTotalHosts () {
560
- return this.getHosts().length
561
- }
562
-
563
- /**
564
- * Returns whether or not the uri passes a test for this.absoluteUriPattern
565
- * @returns {Boolean} True if the path is absolute
566
- */
567
- uriIsAbsolute (uri) {
568
- return this.absoluteUriPattern.test(uri)
569
- }
570
-
571
- /**
572
- * Returns true when the HTTP request is retryable
573
- * @param {Object} options The options
574
- * @param {Number} options.statusCode The HTTP status code
575
- * @param {String} options.errorCode The error code
576
- * @param {String} options.httpMethod The http method
577
- * @returns {Boolean} True if the request is retry able
578
- */
579
- requestIsRetryable (options = {}) {
580
- const {
581
- statusCode,
582
- errorCode,
583
- httpMethod,
584
- } = options
585
- // Honor strictly the http method
586
- if (!this.getRetryableHttpMethods().has(httpMethod)) {
587
- return false
588
- }
589
- if (statusCode && this.getRetryableStatusCodes().has(statusCode)) {
590
- return true
591
- }
592
- if (errorCode && this.getRetryableErrorCodes().has(errorCode)) {
593
- return true
594
- }
595
- return false
596
- }
597
-
598
- /**
599
- * Perform an HTTP request using the provided options
600
- * @param {HttpRequestOptions} [options={}] Options for the HTTP client
601
- * @returns {Promise<{status: Number, body: Object|String}>} An object with a status and body
602
- * property when stream is false and a stream when the stream option is true
603
- * @throws {ERROR_HTTP_REQUEST_MAX_REDIRECTS} When the maximum number of redirect has been reached
604
- */
605
- async fetch (options = {}) {
606
- const {
607
- body,
608
- headers = {},
609
- httpMethod = HTTP_METHOD_GET,
610
- query,
611
- stream = false,
612
- timeout = DEAULT_TIMEOUT,
613
- useLeader = false,
614
- retries = this.getTotalHosts() * 3,
615
- maxRedirects = 10,
616
- attempt = 0,
617
- retryAttempt = 0,
618
- redirectAttempt = 0,
619
- attemptHostIndex,
620
- exponentailBackoffExponent = this.getExponentailBackoffExponent(),
621
- exponentailBackoffBase = this.getExponentailBackoffBase(),
622
- httpAgent = this.getHttpAgent(),
623
- httpsAgent = this.getHttpsAgent(),
624
- } = options
625
- // Honor the supplied attemptHostIndex or get the active host
626
- const activeHost = Number.isFinite(attemptHostIndex) ? this.getHosts()[attemptHostIndex]
627
- : this.getActiveHost(useLeader)
628
- let { uri } = options
629
- if (!uri) {
630
- throw new Error('The uri option is required')
631
- }
632
- uri = this.uriIsAbsolute(uri) ? uri : `${activeHost}/${cleanPath(uri)}`
633
- try {
634
- let auth
635
- if (this.authentication.size) {
636
- auth = {
637
- username: this.authentication.get('username'),
638
- password: this.authentication.get('password'),
639
- }
640
- }
641
- const response = await axios({
642
- url: uri,
643
- auth,
644
- data: body,
645
- maxRedirects: 0, // Handle redirects manually to allow reposting data
646
- headers: createDefaultHeaders(headers),
647
- responseType: stream ? 'stream' : 'json',
648
- method: httpMethod,
649
- params: query,
650
- timeout,
651
- httpsAgent,
652
- httpAgent,
653
- paramsSerializer (params) {
654
- return stringifyQuery(params, { arrayFormat: 'brackets' })
655
- },
656
- })
657
- if (stream) {
658
- return response.data
659
- }
660
- return {
661
- body: response.data,
662
- status: response.status,
663
- }
664
- } catch (e) {
665
- const { response = {}, code: errorCode } = e
666
- const { status: responseStatus, headers: responseHeaders = {} } = response
667
- // Check if the error was a redirect
668
- const retryable = this.requestIsRetryable({
669
- statusCode: responseStatus,
670
- errorCode,
671
- httpMethod,
672
- })
673
- // Save the next active host index and pass it to retry manually
674
- let nextAttemptHostIndex = Number.isFinite(attemptHostIndex) ? attemptHostIndex
675
- : this.getActiveHostIndex()
676
- nextAttemptHostIndex += 1
677
- // We go past the last index start from zero
678
- if (nextAttemptHostIndex === this.getTotalHosts()) {
679
- nextAttemptHostIndex = 0
680
- }
681
- // First check if this is a redirect error
682
- if (responseStatus === 301 || responseStatus === 302) {
683
- // We maxed out on redirect attempts
684
- if (redirectAttempt >= maxRedirects) {
685
- throw ERROR_HTTP_REQUEST_MAX_REDIRECTS(`The maximum number of redirects ${maxRedirects} has been reached`)
686
- }
687
- const location = typeof responseHeaders === 'object' ? responseHeaders.location : undefined
688
- // If we were asked to use the leader, but got redirect the leader moved so remember it
689
- if (useLeader) {
690
- const newLeaderHostIndex = this.findHostIndex(location)
691
- // If the redirect exists in the hosts list remember it for next time
692
- if (newLeaderHostIndex > -1) {
693
- this.setLeaderHostIndex(newLeaderHostIndex)
694
- }
695
- }
696
- return this.fetch({
697
- ...options,
698
- uri: location,
699
- attempt: attempt + 1,
700
- redirectAttempt: redirectAttempt + 1,
701
- attemptHostIndex: nextAttemptHostIndex,
702
- })
703
- }
704
- if (retryable && retryAttempt < retries) {
705
- const waitTime = getWaitTimeExponential(
706
- retryAttempt,
707
- exponentailBackoffBase,
708
- exponentailBackoffExponent,
709
- )
710
- await new Promise((resolve) => {
711
- setTimeout(() => {
712
- resolve(undefined)
713
- }, waitTime)
714
- })
715
- return this.fetch({
716
- ...options,
717
- attempt: attempt + 1,
718
- retryAttempt: retryAttempt + 1,
719
- attemptHostIndex: nextAttemptHostIndex,
720
- })
721
- }
722
- throw e
723
- }
724
- }
725
-
726
- /**
727
- * Perform an HTTP GET request
728
- * @param {HttpRequestOptions} [options={}] The options
729
- * @see this.fetch() for options
730
- */
731
- async get (options = {}) {
732
- return this.fetch({ ...options, httpMethod: HTTP_METHOD_GET })
733
- }
734
-
735
- /**
736
- * Perform an HTTP POST request
737
- * @param {HttpRequestOptions} [options={}] The options
738
- * @see this.fetch() for options
739
- */
740
- async post (options = {}) {
741
- return this.fetch({ ...options, httpMethod: HTTP_METHOD_POST })
742
- }
743
- }