@elastic/elasticsearch 7.7.0-rc.2 → 7.8.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 (49) hide show
  1. package/README.md +2 -0
  2. package/api/api/async_search.submit.js +4 -0
  3. package/api/api/autoscaling.delete_autoscaling_policy.js +78 -0
  4. package/api/api/autoscaling.get_autoscaling_policy.js +78 -0
  5. package/api/api/autoscaling.put_autoscaling_policy.js +82 -0
  6. package/api/api/bulk.js +4 -0
  7. package/api/api/cluster.delete_component_template.js +1 -1
  8. package/api/api/cluster.delete_voting_config_exclusions.js +79 -0
  9. package/api/api/cluster.exists_component_template.js +86 -0
  10. package/api/api/cluster.get_component_template.js +1 -1
  11. package/api/api/cluster.post_voting_config_exclusions.js +82 -0
  12. package/api/api/cluster.put_component_template.js +1 -1
  13. package/api/api/delete_by_query.js +4 -0
  14. package/api/api/eql.search.js +1 -1
  15. package/api/api/exists.js +4 -0
  16. package/api/api/exists_source.js +4 -0
  17. package/api/api/explain.js +4 -0
  18. package/api/api/get.js +4 -0
  19. package/api/api/get_source.js +4 -0
  20. package/api/api/indices.delete_index_template.js +86 -0
  21. package/api/api/indices.exists_index_template.js +88 -0
  22. package/api/api/indices.get_index_template.js +87 -0
  23. package/api/api/indices.put_index_template.js +91 -0
  24. package/api/api/indices.simulate_index_template.js +87 -0
  25. package/api/api/mget.js +4 -0
  26. package/api/api/ml.delete_data_frame_analytics.js +2 -1
  27. package/api/api/ml.validate.js +1 -0
  28. package/api/api/ml.validate_detector.js +1 -0
  29. package/api/api/search.js +4 -0
  30. package/api/api/searchable_snapshots.clear_cache.js +83 -0
  31. package/api/api/searchable_snapshots.mount.js +94 -0
  32. package/api/api/searchable_snapshots.repository_stats.js +78 -0
  33. package/api/api/searchable_snapshots.stats.js +77 -0
  34. package/api/api/snapshot.cleanup_repository.js +1 -1
  35. package/api/api/tasks.cancel.js +2 -0
  36. package/api/api/update.js +4 -0
  37. package/api/api/update_by_query.js +4 -0
  38. package/api/index.js +39 -1
  39. package/api/requestParams.d.ts +119 -3
  40. package/index.d.ts +156 -16
  41. package/index.js +9 -2
  42. package/lib/Connection.js +8 -2
  43. package/lib/Helpers.d.ts +22 -5
  44. package/lib/Helpers.js +328 -32
  45. package/lib/Transport.js +30 -15
  46. package/lib/pool/BaseConnectionPool.js +2 -0
  47. package/lib/pool/CloudConnectionPool.js +1 -1
  48. package/lib/pool/ConnectionPool.js +12 -2
  49. package/package.json +21 -13
package/lib/Helpers.js CHANGED
@@ -4,13 +4,16 @@
4
4
 
5
5
  'use strict'
6
6
 
7
+ /* eslint camelcase: 0 */
8
+
9
+ const { Readable } = require('stream')
7
10
  const { promisify } = require('util')
8
11
  const { ResponseError, ConfigurationError } = require('./errors')
9
12
 
10
13
  const pImmediate = promisify(setImmediate)
11
14
  const sleep = promisify(setTimeout)
12
- const kGetHits = Symbol('elasticsearch-get-hits')
13
15
  const kClient = Symbol('elasticsearch-client')
16
+ /* istanbul ignore next */
14
17
  const noop = () => {}
15
18
 
16
19
  class Helpers {
@@ -19,23 +22,22 @@ class Helpers {
19
22
  this.maxRetries = opts.maxRetries
20
23
  }
21
24
 
22
- [kGetHits] (body) {
23
- if (body.hits && body.hits.hits) {
24
- return body.hits.hits.map(d => d._source)
25
- }
26
- return []
27
- }
28
-
29
25
  /**
30
26
  * Runs a search operation. The only difference between client.search and this utility,
31
27
  * is that we are only returning the hits to the user and not the full ES response.
28
+ * This helper automatically adds `filter_path=hits.hits._source` to the querystring,
29
+ * as it will only need the documents source.
32
30
  * @param {object} params - The Elasticsearch's search parameters.
33
31
  * @param {object} options - The client optional configuration for this request.
34
32
  * @return {array} The documents that matched the request.
35
33
  */
36
34
  async search (params, options) {
37
- const response = await this[kClient].search(params, options)
38
- return this[kGetHits](response.body)
35
+ appendFilterPath('hits.hits._source', params, true)
36
+ const { body } = await this[kClient].search(params, options)
37
+ if (body.hits && body.hits.hits) {
38
+ return body.hits.hits.map(d => d._source)
39
+ }
40
+ return []
39
41
  }
40
42
 
41
43
  /**
@@ -63,6 +65,8 @@ class Helpers {
63
65
  options.ignore = [429]
64
66
  }
65
67
  params.scroll = params.scroll || '1m'
68
+ appendFilterPath('_scroll_id', params, false)
69
+ const { method, body, index, ...querystring } = params
66
70
 
67
71
  let response = null
68
72
  for (let i = 0; i < maxRetries; i++) {
@@ -74,33 +78,31 @@ class Helpers {
74
78
  throw new ResponseError(response)
75
79
  }
76
80
 
77
- let scrollId = response.body._scroll_id
81
+ let scroll_id = response.body._scroll_id
78
82
  let stop = false
79
83
  const clear = async () => {
80
84
  stop = true
81
85
  await this[kClient].clearScroll(
82
- { body: { scroll_id: scrollId } },
86
+ { body: { scroll_id } },
83
87
  { ignore: [400] }
84
88
  )
85
89
  }
86
90
 
87
- while (response.body.hits.hits.length > 0) {
88
- scrollId = response.body._scroll_id
91
+ while (response.body.hits && response.body.hits.hits.length > 0) {
92
+ scroll_id = response.body._scroll_id
89
93
  response.clear = clear
90
- response.documents = this[kGetHits](response.body)
94
+ addDocumentsGetter(response)
91
95
 
92
96
  yield response
93
97
 
94
- if (!scrollId || stop === true) {
98
+ if (!scroll_id || stop === true) {
95
99
  break
96
100
  }
97
101
 
98
102
  for (let i = 0; i < maxRetries; i++) {
99
103
  response = await this[kClient].scroll({
100
- scroll: params.scroll,
101
- body: {
102
- scroll_id: scrollId
103
- }
104
+ ...querystring,
105
+ body: { scroll_id }
104
106
  }, options)
105
107
  if (response.statusCode !== 429) break
106
108
  await sleep(wait)
@@ -120,11 +122,14 @@ class Helpers {
120
122
  * }
121
123
  * ```
122
124
  * Each document is what you will find by running a scrollSearch and iterating on the hits array.
125
+ * This helper automatically adds `filter_path=hits.hits._source` to the querystring,
126
+ * as it will only need the documents source.
123
127
  * @param {object} params - The Elasticsearch's search parameters.
124
128
  * @param {object} options - The client optional configuration for this request.
125
129
  * @return {iterator} the async iterator
126
130
  */
127
131
  async * scrollDocuments (params, options) {
132
+ appendFilterPath('hits.hits._source', params, true)
128
133
  for await (const { documents } of this.scrollSearch(params)) {
129
134
  for (const document of documents) {
130
135
  yield document
@@ -132,21 +137,266 @@ class Helpers {
132
137
  }
133
138
  }
134
139
 
140
+ /**
141
+ * Creates a msearch helper instance. Once you configure it, you can use the provided
142
+ * `search` method to add new searches in the queue.
143
+ * @param {object} options - The configuration of the msearch operations.
144
+ * @return {object} The possible operations to run.
145
+ */
146
+ msearch (options = {}) {
147
+ const client = this[kClient]
148
+ const {
149
+ operations = 5,
150
+ concurrency = 5,
151
+ flushInterval = 500,
152
+ retries = this.maxRetries,
153
+ wait = 5000,
154
+ ...msearchOptions
155
+ } = options
156
+
157
+ let stopReading = false
158
+ let stopError = null
159
+ let timeoutRef = null
160
+ const operationsStream = new Readable({
161
+ objectMode: true,
162
+ read (size) {}
163
+ })
164
+
165
+ const p = iterate()
166
+ const helper = {
167
+ then (onFulfilled, onRejected) {
168
+ return p.then(onFulfilled, onRejected)
169
+ },
170
+ catch (onRejected) {
171
+ return p.catch(onRejected)
172
+ },
173
+ stop (error = null) {
174
+ if (stopReading === true) return
175
+ stopReading = true
176
+ stopError = error
177
+ operationsStream.push(null)
178
+ },
179
+ // TODO: support abort a single search?
180
+ // NOTE: the validation checks are synchronous and the callback/promise will
181
+ // be resolved in the same tick. We might want to fix this in the future.
182
+ search (header, body, callback) {
183
+ if (stopReading === true) {
184
+ const error = stopError === null
185
+ ? new ConfigurationError('The msearch processor has been stopped')
186
+ : stopError
187
+ return callback ? callback(error, {}) : Promise.reject(error)
188
+ }
189
+
190
+ if (!(typeof header === 'object' && header !== null && !Array.isArray(header))) {
191
+ const error = new ConfigurationError('The header should be an object')
192
+ return callback ? callback(error, {}) : Promise.reject(error)
193
+ }
194
+
195
+ if (!(typeof body === 'object' && body !== null && !Array.isArray(body))) {
196
+ const error = new ConfigurationError('The body should be an object')
197
+ return callback ? callback(error, {}) : Promise.reject(error)
198
+ }
199
+
200
+ let promise = null
201
+ if (callback === undefined) {
202
+ let onFulfilled = null
203
+ let onRejected = null
204
+ promise = new Promise((resolve, reject) => {
205
+ onFulfilled = resolve
206
+ onRejected = reject
207
+ })
208
+ callback = function callback (err, result) {
209
+ err ? onRejected(err) : onFulfilled(result)
210
+ }
211
+ }
212
+
213
+ operationsStream.push([header, body, callback])
214
+
215
+ if (promise !== null) {
216
+ return promise
217
+ }
218
+ }
219
+ }
220
+
221
+ return helper
222
+
223
+ async function iterate () {
224
+ const { semaphore, finish } = buildSemaphore()
225
+ const msearchBody = []
226
+ const callbacks = []
227
+ let loadedOperations = 0
228
+ timeoutRef = setTimeout(onFlushTimeout, flushInterval)
229
+
230
+ for await (const operation of operationsStream) {
231
+ timeoutRef.refresh()
232
+ loadedOperations += 1
233
+ msearchBody.push(operation[0], operation[1])
234
+ callbacks.push(operation[2])
235
+ if (loadedOperations >= operations) {
236
+ const send = await semaphore()
237
+ send(msearchBody.slice(), callbacks.slice())
238
+ msearchBody.length = 0
239
+ callbacks.length = 0
240
+ loadedOperations = 0
241
+ }
242
+ }
243
+
244
+ clearTimeout(timeoutRef)
245
+ // In some cases the previos http call does not have finished,
246
+ // or we didn't reach the flush bytes threshold, so we force one last operation.
247
+ if (loadedOperations > 0) {
248
+ const send = await semaphore()
249
+ send(msearchBody, callbacks)
250
+ }
251
+
252
+ await finish()
253
+
254
+ if (stopError !== null) {
255
+ throw stopError
256
+ }
257
+
258
+ async function onFlushTimeout () {
259
+ if (loadedOperations === 0) return
260
+ const msearchBodyCopy = msearchBody.slice()
261
+ const callbacksCopy = callbacks.slice()
262
+ msearchBody.length = 0
263
+ callbacks.length = 0
264
+ loadedOperations = 0
265
+ try {
266
+ const send = await semaphore()
267
+ send(msearchBodyCopy, callbacksCopy)
268
+ } catch (err) {
269
+ /* istanbul ignore next */
270
+ helper.stop(err)
271
+ }
272
+ }
273
+ }
274
+
275
+ // This function builds a semaphore using the concurrency
276
+ // options of the msearch helper. It is used inside the iterator
277
+ // to guarantee that no more than the number of operations
278
+ // allowed to run at the same time are executed.
279
+ // It returns a semaphore function which resolves in the next tick
280
+ // if we didn't reach the maximim concurrency yet, otherwise it returns
281
+ // a promise that resolves as soon as one of the running request has finshed.
282
+ // The semaphore function resolves a send function, which will be used
283
+ // to send the actual msearch request.
284
+ // It also returns a finish function, which returns a promise that is resolved
285
+ // when there are no longer request running.
286
+ function buildSemaphore () {
287
+ let resolveSemaphore = null
288
+ let resolveFinish = null
289
+ let running = 0
290
+
291
+ return { semaphore, finish }
292
+
293
+ function finish () {
294
+ return new Promise((resolve, reject) => {
295
+ if (running === 0) {
296
+ resolve()
297
+ } else {
298
+ resolveFinish = resolve
299
+ }
300
+ })
301
+ }
302
+
303
+ function semaphore () {
304
+ if (running < concurrency) {
305
+ running += 1
306
+ return pImmediate(send)
307
+ } else {
308
+ return new Promise((resolve, reject) => {
309
+ resolveSemaphore = resolve
310
+ })
311
+ }
312
+ }
313
+
314
+ function send (msearchBody, callbacks) {
315
+ /* istanbul ignore if */
316
+ if (running > concurrency) {
317
+ throw new Error('Max concurrency reached')
318
+ }
319
+ msearchOperation(msearchBody, callbacks, () => {
320
+ running -= 1
321
+ if (resolveSemaphore) {
322
+ running += 1
323
+ resolveSemaphore(send)
324
+ resolveSemaphore = null
325
+ } else if (resolveFinish && running === 0) {
326
+ resolveFinish()
327
+ }
328
+ })
329
+ }
330
+ }
331
+
332
+ function msearchOperation (msearchBody, callbacks, done) {
333
+ let retryCount = retries
334
+
335
+ // Instead of going full on async-await, which would make the code easier to read,
336
+ // we have decided to use callback style instead.
337
+ // This because every time we use async await, V8 will create multiple promises
338
+ // behind the scenes, making the code slightly slower.
339
+ tryMsearch(msearchBody, callbacks, retrySearch)
340
+ function retrySearch (msearchBody, callbacks) {
341
+ if (msearchBody.length > 0 && retryCount > 0) {
342
+ retryCount -= 1
343
+ setTimeout(tryMsearch, wait, msearchBody, callbacks, retrySearch)
344
+ return
345
+ }
346
+
347
+ done()
348
+ }
349
+
350
+ // This function never returns an error, if the msearch operation fails,
351
+ // the error is dispatched to all search executors.
352
+ function tryMsearch (msearchBody, callbacks, done) {
353
+ client.msearch(Object.assign({}, msearchOptions, { body: msearchBody }), (err, results) => {
354
+ const retryBody = []
355
+ const retryCallbacks = []
356
+ if (err) {
357
+ addDocumentsGetter(results)
358
+ for (const callback of callbacks) {
359
+ callback(err, results)
360
+ }
361
+ return done(retryBody, retryCallbacks)
362
+ }
363
+ const { responses } = results.body
364
+ for (let i = 0, len = responses.length; i < len; i++) {
365
+ const response = responses[i]
366
+ if (response.status === 429 && retryCount > 0) {
367
+ retryBody.push(msearchBody[i * 2])
368
+ retryBody.push(msearchBody[(i * 2) + 1])
369
+ retryCallbacks.push(callbacks[i])
370
+ continue
371
+ }
372
+ const result = { ...results, body: response }
373
+ addDocumentsGetter(result)
374
+ if (response.status >= 400) {
375
+ callbacks[i](new ResponseError(result), result)
376
+ } else {
377
+ callbacks[i](null, result)
378
+ }
379
+ }
380
+ done(retryBody, retryCallbacks)
381
+ })
382
+ }
383
+ }
384
+ }
385
+
135
386
  /**
136
387
  * Creates a bulk helper instance. Once you configure it, you can pick which operation
137
388
  * to execute with the given dataset, index, create, update, and delete.
138
389
  * @param {object} options - The configuration of the bulk operation.
139
- * @return {object} The possible orations to run with the datasource.
390
+ * @return {object} The possible operations to run with the datasource.
140
391
  */
141
392
  bulk (options) {
142
- // TODO: add an interval to force flush the body
143
- // to handle the slow producer problem
144
393
  const client = this[kClient]
145
394
  const { serialize, deserialize } = client.serializer
146
395
  const {
147
396
  datasource,
148
397
  onDocument,
149
398
  flushBytes = 5000000,
399
+ flushInterval = 30000,
150
400
  concurrency = 5,
151
401
  retries = this.maxRetries,
152
402
  wait = 5000,
@@ -166,6 +416,7 @@ class Helpers {
166
416
  }
167
417
 
168
418
  let shouldAbort = false
419
+ let timeoutRef = null
169
420
  const stats = {
170
421
  total: 0,
171
422
  failed: 0,
@@ -177,8 +428,7 @@ class Helpers {
177
428
  }
178
429
 
179
430
  const p = iterate()
180
-
181
- return {
431
+ const helper = {
182
432
  then (onFulfilled, onRejected) {
183
433
  return p.then(onFulfilled, onRejected)
184
434
  },
@@ -186,12 +436,15 @@ class Helpers {
186
436
  return p.catch(onRejected)
187
437
  },
188
438
  abort () {
439
+ clearTimeout(timeoutRef)
189
440
  shouldAbort = true
190
441
  stats.aborted = true
191
442
  return this
192
443
  }
193
444
  }
194
445
 
446
+ return helper
447
+
195
448
  /**
196
449
  * Function that iterates over the given datasource and start a bulk operation as soon
197
450
  * as it reaches the configured bulk size. It's designed to use the Node.js asynchronous
@@ -208,9 +461,11 @@ class Helpers {
208
461
  let actionBody = ''
209
462
  let payloadBody = ''
210
463
  let chunkBytes = 0
464
+ timeoutRef = setTimeout(onFlushTimeout, flushInterval)
211
465
 
212
466
  for await (const chunk of datasource) {
213
467
  if (shouldAbort === true) break
468
+ timeoutRef.refresh()
214
469
  const action = onDocument(chunk)
215
470
  const operation = Array.isArray(action)
216
471
  ? Object.keys(action[0])[0]
@@ -219,21 +474,20 @@ class Helpers {
219
474
  actionBody = serialize(action)
220
475
  payloadBody = typeof chunk === 'string' ? chunk : serialize(chunk)
221
476
  chunkBytes += Buffer.byteLength(actionBody) + Buffer.byteLength(payloadBody)
222
- bulkBody.push(actionBody)
223
- bulkBody.push(payloadBody)
477
+ bulkBody.push(actionBody, payloadBody)
224
478
  } else if (operation === 'update') {
225
479
  actionBody = serialize(action[0])
226
480
  payloadBody = typeof chunk === 'string'
227
- ? `{doc:${chunk}}`
481
+ ? `{"doc":${chunk}}`
228
482
  : serialize({ doc: chunk, ...action[1] })
229
483
  chunkBytes += Buffer.byteLength(actionBody) + Buffer.byteLength(payloadBody)
230
- bulkBody.push(actionBody)
231
- bulkBody.push(payloadBody)
484
+ bulkBody.push(actionBody, payloadBody)
232
485
  } else if (operation === 'delete') {
233
486
  actionBody = serialize(action)
234
487
  chunkBytes += Buffer.byteLength(actionBody)
235
488
  bulkBody.push(actionBody)
236
489
  } else {
490
+ clearTimeout(timeoutRef)
237
491
  throw new ConfigurationError(`Bulk helper invalid action: '${operation}'`)
238
492
  }
239
493
 
@@ -246,6 +500,7 @@ class Helpers {
246
500
  }
247
501
  }
248
502
 
503
+ clearTimeout(timeoutRef)
249
504
  // In some cases the previos http call does not have finished,
250
505
  // or we didn't reach the flush bytes threshold, so we force one last operation.
251
506
  if (shouldAbort === false && chunkBytes > 0) {
@@ -268,6 +523,21 @@ class Helpers {
268
523
  stats.total = stats.successful + stats.failed
269
524
 
270
525
  return stats
526
+
527
+ async function onFlushTimeout () {
528
+ if (chunkBytes === 0) return
529
+ stats.bytes += chunkBytes
530
+ const bulkBodyCopy = bulkBody.slice()
531
+ bulkBody.length = 0
532
+ chunkBytes = 0
533
+ try {
534
+ const send = await semaphore()
535
+ send(bulkBodyCopy)
536
+ } catch (err) {
537
+ /* istanbul ignore next */
538
+ helper.abort()
539
+ }
540
+ }
271
541
  }
272
542
 
273
543
  // This function builds a semaphore using the concurrency
@@ -308,6 +578,7 @@ class Helpers {
308
578
 
309
579
  function semaphore () {
310
580
  if (running < concurrency) {
581
+ running += 1
311
582
  return pImmediate(send)
312
583
  } else {
313
584
  return new Promise((resolve, reject) => {
@@ -318,10 +589,9 @@ class Helpers {
318
589
 
319
590
  function send (bulkBody) {
320
591
  /* istanbul ignore if */
321
- if (running >= concurrency) {
592
+ if (running > concurrency) {
322
593
  throw new Error('Max concurrency reached')
323
594
  }
324
- running += 1
325
595
  bulkOperation(bulkBody, err => {
326
596
  running -= 1
327
597
  if (err) {
@@ -329,6 +599,7 @@ class Helpers {
329
599
  error = err
330
600
  }
331
601
  if (resolveSemaphore) {
602
+ running += 1
332
603
  resolveSemaphore(send)
333
604
  resolveSemaphore = null
334
605
  } else if (resolveFinish && running === 0) {
@@ -371,6 +642,7 @@ class Helpers {
371
642
  operation: deserialize(bulkBody[i]),
372
643
  document: operation !== 'delete'
373
644
  ? deserialize(bulkBody[i + 1])
645
+ /* istanbul ignore next */
374
646
  : null,
375
647
  retried: isRetrying
376
648
  })
@@ -402,6 +674,7 @@ class Helpers {
402
674
  // but the ES node were handling too many operations.
403
675
  if (status === 429) {
404
676
  retry.push(bulkBody[indexSlice])
677
+ /* istanbul ignore next */
405
678
  if (operation !== 'delete') {
406
679
  retry.push(bulkBody[indexSlice + 1])
407
680
  }
@@ -428,4 +701,27 @@ class Helpers {
428
701
  }
429
702
  }
430
703
 
704
+ // Using a getter will improve the overall performances of the code,
705
+ // as we will reed the documents only if needed.
706
+ function addDocumentsGetter (result) {
707
+ Object.defineProperty(result, 'documents', {
708
+ get () {
709
+ if (this.body.hits && this.body.hits.hits) {
710
+ return this.body.hits.hits.map(d => d._source)
711
+ }
712
+ return []
713
+ }
714
+ })
715
+ }
716
+
717
+ function appendFilterPath (filter, params, force) {
718
+ if (params.filter_path !== undefined) {
719
+ params.filter_path += ',' + filter
720
+ } else if (params.filterPath !== undefined) {
721
+ params.filterPath += ',' + filter
722
+ } else if (force === true) {
723
+ params.filter_path = filter
724
+ }
725
+ }
726
+
431
727
  module.exports = Helpers
package/lib/Transport.js CHANGED
@@ -22,7 +22,7 @@ const clientVersion = require('../package.json').version
22
22
  const userAgent = `elasticsearch-js/${clientVersion} (${os.platform()} ${os.release()}-${os.arch()}; Node.js ${process.version})`
23
23
 
24
24
  class Transport {
25
- constructor (opts = {}) {
25
+ constructor (opts) {
26
26
  if (typeof opts.compression === 'string' && opts.compression !== 'gzip') {
27
27
  throw new ConfigurationError(`Invalid compression: '${opts.compression}'`)
28
28
  }
@@ -34,9 +34,9 @@ class Transport {
34
34
  this.suggestCompression = opts.suggestCompression === true
35
35
  this.compression = opts.compression || false
36
36
  this.headers = Object.assign({},
37
- { 'User-Agent': userAgent },
38
- opts.suggestCompression === true ? { 'Accept-Encoding': 'gzip,deflate' } : null,
39
- opts.headers
37
+ { 'user-agent': userAgent },
38
+ opts.suggestCompression === true ? { 'accept-encoding': 'gzip,deflate' } : null,
39
+ lowerCaseHeaders(opts.headers)
40
40
  )
41
41
  this.sniffInterval = opts.sniffInterval
42
42
  this.sniffOnConnectionFault = opts.sniffOnConnectionFault
@@ -51,7 +51,6 @@ class Transport {
51
51
  } else if (opts.nodeSelector === 'round-robin') {
52
52
  this.nodeSelector = roundRobinSelector()
53
53
  } else if (opts.nodeSelector === 'random') {
54
- /* istanbul ignore next */
55
54
  this.nodeSelector = randomSelector
56
55
  } else {
57
56
  this.nodeSelector = roundRobinSelector()
@@ -243,10 +242,10 @@ class Transport {
243
242
  })
244
243
  }
245
244
 
246
- const headers = Object.assign({}, this.headers, options.headers)
245
+ const headers = Object.assign({}, this.headers, lowerCaseHeaders(options.headers))
247
246
 
248
247
  if (options.opaqueId !== undefined) {
249
- headers['X-Opaque-Id'] = this.opaqueIdPrefix !== null
248
+ headers['x-opaque-id'] = this.opaqueIdPrefix !== null
250
249
  ? this.opaqueIdPrefix + options.opaqueId
251
250
  : options.opaqueId
252
251
  }
@@ -262,7 +261,7 @@ class Transport {
262
261
  }
263
262
 
264
263
  if (params.body !== '') {
265
- headers['Content-Type'] = headers['Content-Type'] || 'application/json'
264
+ headers['content-type'] = headers['content-type'] || 'application/json'
266
265
  }
267
266
 
268
267
  // handle ndjson body
@@ -277,7 +276,7 @@ class Transport {
277
276
  params.body = params.bulkBody
278
277
  }
279
278
  if (params.body !== '') {
280
- headers['Content-Type'] = headers['Content-Type'] || 'application/x-ndjson'
279
+ headers['content-type'] = headers['content-type'] || 'application/x-ndjson'
281
280
  }
282
281
  }
283
282
 
@@ -301,7 +300,7 @@ class Transport {
301
300
  if (params.body !== '' && params.body != null) {
302
301
  if (isStream(params.body) === true) {
303
302
  if (compression === 'gzip') {
304
- params.headers['Content-Encoding'] = compression
303
+ params.headers['content-encoding'] = compression
305
304
  params.body = params.body.pipe(createGzip())
306
305
  }
307
306
  makeRequest()
@@ -311,13 +310,13 @@ class Transport {
311
310
  if (err) {
312
311
  return callback(err, result)
313
312
  }
314
- params.headers['Content-Encoding'] = compression
315
- params.headers['Content-Length'] = '' + Buffer.byteLength(buffer)
313
+ params.headers['content-encoding'] = compression
314
+ params.headers['content-length'] = '' + Buffer.byteLength(buffer)
316
315
  params.body = buffer
317
316
  makeRequest()
318
317
  })
319
318
  } else {
320
- params.headers['Content-Length'] = '' + Buffer.byteLength(params.body)
319
+ params.headers['content-length'] = '' + Buffer.byteLength(params.body)
321
320
  makeRequest()
322
321
  }
323
322
  } else {
@@ -385,7 +384,7 @@ class Transport {
385
384
  }
386
385
 
387
386
  debug('Sniffing ended successfully', result.body)
388
- const protocol = result.meta.connection.url.protocol || 'http:'
387
+ const protocol = result.meta.connection.url.protocol || /* istanbul ignore next */ 'http:'
389
388
  const hosts = this.connectionPool.nodesToHost(result.body.nodes, protocol)
390
389
  this.connectionPool.update(hosts)
391
390
 
@@ -453,5 +452,21 @@ function generateRequestId () {
453
452
  return (nextReqId = (nextReqId + 1) & maxInt)
454
453
  }
455
454
  }
455
+
456
+ function lowerCaseHeaders (oldHeaders) {
457
+ if (oldHeaders == null) return oldHeaders
458
+ const newHeaders = {}
459
+ for (const header in oldHeaders) {
460
+ newHeaders[header.toLowerCase()] = oldHeaders[header]
461
+ }
462
+ return newHeaders
463
+ }
464
+
456
465
  module.exports = Transport
457
- module.exports.internals = { defaultNodeFilter, roundRobinSelector, randomSelector, generateRequestId }
466
+ module.exports.internals = {
467
+ defaultNodeFilter,
468
+ roundRobinSelector,
469
+ randomSelector,
470
+ generateRequestId,
471
+ lowerCaseHeaders
472
+ }
@@ -52,6 +52,7 @@ class BaseConnectionPool {
52
52
  }
53
53
 
54
54
  if (opts.ssl == null) opts.ssl = this._ssl
55
+ /* istanbul ignore else */
55
56
  if (opts.agent == null) opts.agent = this._agent
56
57
 
57
58
  const connection = new this.Connection(opts)
@@ -201,6 +202,7 @@ class BaseConnectionPool {
201
202
  }
202
203
 
203
204
  address = address.slice(0, 4) === 'http'
205
+ /* istanbul ignore next */
204
206
  ? address
205
207
  : `${protocol}//${address}`
206
208
  const roles = node.roles.reduce((acc, role) => {
@@ -7,7 +7,7 @@
7
7
  const BaseConnectionPool = require('./BaseConnectionPool')
8
8
 
9
9
  class CloudConnectionPool extends BaseConnectionPool {
10
- constructor (opts = {}) {
10
+ constructor (opts) {
11
11
  super(opts)
12
12
  this.cloudConnection = null
13
13
  }