@terascope/elasticsearch-api 2.24.2 → 3.0.2
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 +127 -53
- package/package.json +3 -3
- package/test/api-spec.js +137 -81
- package/types/index.d.ts +34 -1
package/index.js
CHANGED
|
@@ -19,14 +19,17 @@ const {
|
|
|
19
19
|
random,
|
|
20
20
|
cloneDeep,
|
|
21
21
|
DataEntity,
|
|
22
|
-
isDeepEqual
|
|
22
|
+
isDeepEqual,
|
|
23
|
+
getTypeOf,
|
|
24
|
+
isProd
|
|
23
25
|
} = require('@terascope/utils');
|
|
26
|
+
const { inspect } = require('util');
|
|
24
27
|
|
|
25
28
|
const DOCUMENT_EXISTS = 409;
|
|
26
29
|
|
|
27
30
|
// Module to manage persistence in Elasticsearch.
|
|
28
31
|
// All functions in this module return promises that must be resolved to get the final result.
|
|
29
|
-
module.exports = function elasticsearchApi(client
|
|
32
|
+
module.exports = function elasticsearchApi(client, logger, _opConfig) {
|
|
30
33
|
const config = _opConfig || {};
|
|
31
34
|
if (!client) {
|
|
32
35
|
throw new Error('Elasticsearch API requires client');
|
|
@@ -98,7 +101,7 @@ module.exports = function elasticsearchApi(client = {}, logger, _opConfig) {
|
|
|
98
101
|
|
|
99
102
|
function _runRequest() {
|
|
100
103
|
clientBase[endpoint](query)
|
|
101
|
-
.then(
|
|
104
|
+
.then(resolve)
|
|
102
105
|
.catch(errHandler);
|
|
103
106
|
}
|
|
104
107
|
|
|
@@ -211,7 +214,7 @@ module.exports = function elasticsearchApi(client = {}, logger, _opConfig) {
|
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
function version() {
|
|
214
|
-
const wildCardRegex =
|
|
217
|
+
const wildCardRegex = /\*/g;
|
|
215
218
|
const isWildCardRegexSearch = config.index.match(wildCardRegex);
|
|
216
219
|
// We cannot reliable search index queries with wildcards
|
|
217
220
|
// for existence or max_result_window, it could be
|
|
@@ -259,12 +262,26 @@ module.exports = function elasticsearchApi(client = {}, logger, _opConfig) {
|
|
|
259
262
|
);
|
|
260
263
|
}
|
|
261
264
|
|
|
262
|
-
|
|
265
|
+
/**
|
|
266
|
+
* When the bulk request has errors this will find the actions
|
|
267
|
+
* records to retry.
|
|
268
|
+
*
|
|
269
|
+
* @returns {{
|
|
270
|
+
* retry: Record<string, any>[],
|
|
271
|
+
* successful: number,
|
|
272
|
+
* error: boolean,
|
|
273
|
+
* reason?: string
|
|
274
|
+
* }}
|
|
275
|
+
*/
|
|
276
|
+
function _filterRetryRecords(actionRecords, result) {
|
|
263
277
|
const retry = [];
|
|
264
|
-
const { items } =
|
|
278
|
+
const { items } = result;
|
|
279
|
+
|
|
265
280
|
let nonRetriableError = false;
|
|
266
281
|
let reason = '';
|
|
267
|
-
|
|
282
|
+
let successful = 0;
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < items.length; i++) {
|
|
268
285
|
// key could either be create or delete etc, just want the actual data at the value spot
|
|
269
286
|
const item = Object.values(items[i])[0];
|
|
270
287
|
if (item.error) {
|
|
@@ -275,11 +292,14 @@ module.exports = function elasticsearchApi(client = {}, logger, _opConfig) {
|
|
|
275
292
|
}
|
|
276
293
|
|
|
277
294
|
if (item.error.type === 'es_rejected_execution_exception') {
|
|
278
|
-
if (i
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
295
|
+
if (actionRecords[i] == null) {
|
|
296
|
+
// this error should not happen in production,
|
|
297
|
+
// only in tests where the bulk function is mocked
|
|
298
|
+
throw new Error(`Invalid item index (${i}), not found in bulk send records (length: ${actionRecords.length})`);
|
|
282
299
|
}
|
|
300
|
+
// the index in the item list will match the index in the
|
|
301
|
+
// input records
|
|
302
|
+
retry.push(actionRecords[i]);
|
|
283
303
|
} else if (
|
|
284
304
|
item.error.type !== 'document_already_exists_exception'
|
|
285
305
|
&& item.error.type !== 'document_missing_exception'
|
|
@@ -288,55 +308,95 @@ module.exports = function elasticsearchApi(client = {}, logger, _opConfig) {
|
|
|
288
308
|
reason = `${item.error.type}--${item.error.reason}`;
|
|
289
309
|
break;
|
|
290
310
|
}
|
|
311
|
+
} else if (item.status == null || item.status < 400) {
|
|
312
|
+
successful++;
|
|
291
313
|
}
|
|
292
314
|
}
|
|
293
315
|
|
|
294
316
|
if (nonRetriableError) {
|
|
295
|
-
return {
|
|
317
|
+
return {
|
|
318
|
+
retry: [], successful, error: true, reason
|
|
319
|
+
};
|
|
296
320
|
}
|
|
297
321
|
|
|
298
|
-
return {
|
|
322
|
+
return { retry, successful, error: false };
|
|
299
323
|
}
|
|
300
324
|
|
|
301
|
-
function
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
function _sendData(formattedData) {
|
|
306
|
-
return _clientRequest('bulk', { body: formattedData })
|
|
307
|
-
.then((results) => {
|
|
308
|
-
if (results.errors) {
|
|
309
|
-
const response = _filterResponse(formattedData, results);
|
|
310
|
-
|
|
311
|
-
if (response.error) {
|
|
312
|
-
reject(new TSError(response.reason, {
|
|
313
|
-
retryable: false
|
|
314
|
-
}));
|
|
315
|
-
} else if (response.data.length === 0) {
|
|
316
|
-
// may get doc already created error, if so just return
|
|
317
|
-
resolve(results);
|
|
318
|
-
} else {
|
|
319
|
-
warning();
|
|
320
|
-
retry(response.data);
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
resolve(results);
|
|
324
|
-
}
|
|
325
|
-
})
|
|
326
|
-
.catch((err) => {
|
|
327
|
-
const error = new TSError(err, {
|
|
328
|
-
reason: 'bulk sender error',
|
|
329
|
-
context: {
|
|
330
|
-
connection,
|
|
331
|
-
},
|
|
332
|
-
});
|
|
325
|
+
function getFirstKey(obj) {
|
|
326
|
+
return Object.keys(obj)[0];
|
|
327
|
+
}
|
|
333
328
|
|
|
334
|
-
|
|
335
|
-
|
|
329
|
+
/**
|
|
330
|
+
* @param data {Array<{ action: data }>}
|
|
331
|
+
* @returns {Promise<number>}
|
|
332
|
+
*/
|
|
333
|
+
async function _bulkSend(actionRecords, previousCount = 0, previousRetryDelay = 0) {
|
|
334
|
+
const body = actionRecords.flatMap((record, index) => {
|
|
335
|
+
if (record.action == null) {
|
|
336
|
+
let dbg = '';
|
|
337
|
+
if (!isProd) {
|
|
338
|
+
dbg = `, dbg: ${inspect({ record, index })}`;
|
|
339
|
+
}
|
|
340
|
+
throw new Error(`Bulk send record is missing the action property${dbg}`);
|
|
336
341
|
}
|
|
337
342
|
|
|
338
|
-
|
|
343
|
+
if (getESVersion() >= 7) {
|
|
344
|
+
const actionKey = getFirstKey(record.action);
|
|
345
|
+
const { _type, ...withoutTypeAction } = record.action[actionKey];
|
|
346
|
+
// if data is specified return both
|
|
347
|
+
return record.data ? [{
|
|
348
|
+
...record.action,
|
|
349
|
+
[actionKey]: withoutTypeAction
|
|
350
|
+
}, record.data] : [{
|
|
351
|
+
...record.action,
|
|
352
|
+
[actionKey]: withoutTypeAction
|
|
353
|
+
}];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// if data is specified return both
|
|
357
|
+
return record.data ? [record.action, record.data] : [record.action];
|
|
339
358
|
});
|
|
359
|
+
|
|
360
|
+
const result = await _clientRequest('bulk', { body });
|
|
361
|
+
|
|
362
|
+
if (!result.errors) {
|
|
363
|
+
return result.items.reduce((c, item) => {
|
|
364
|
+
const [value] = Object.values(item);
|
|
365
|
+
// ignore non-successful status codes
|
|
366
|
+
if (value.status != null && value.status >= 400) return c;
|
|
367
|
+
return c + 1;
|
|
368
|
+
}, 0);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const {
|
|
372
|
+
retry, successful, error, reason
|
|
373
|
+
} = _filterRetryRecords(actionRecords, result);
|
|
374
|
+
|
|
375
|
+
if (error) {
|
|
376
|
+
throw new Error(`bulk send error: ${reason}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (retry.length === 0) {
|
|
380
|
+
return previousCount + successful;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
warning();
|
|
384
|
+
|
|
385
|
+
const nextRetryDelay = await _awaitRetry(previousRetryDelay);
|
|
386
|
+
return _bulkSend(retry, previousCount + successful, nextRetryDelay);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* The new and improved bulk send with proper retry support
|
|
391
|
+
*
|
|
392
|
+
* @returns {Promise<number>} the number of affected rows
|
|
393
|
+
*/
|
|
394
|
+
function bulkSend(data) {
|
|
395
|
+
if (!Array.isArray(data)) {
|
|
396
|
+
throw new Error(`Expected bulkSend to receive an array, got ${data} (${getTypeOf(data)})`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return Promise.resolve(_bulkSend(data));
|
|
340
400
|
}
|
|
341
401
|
|
|
342
402
|
function _warn(warnLogger, msg) {
|
|
@@ -622,6 +682,8 @@ module.exports = function elasticsearchApi(client = {}, logger, _opConfig) {
|
|
|
622
682
|
}
|
|
623
683
|
|
|
624
684
|
function _removeTypeFromBulkRequest(query) {
|
|
685
|
+
if (getESVersion() < 7) return query;
|
|
686
|
+
|
|
625
687
|
return query.map((queryItem) => {
|
|
626
688
|
if (isSimpleObject(queryItem)) {
|
|
627
689
|
// get the metadata and ignore the record
|
|
@@ -696,17 +758,29 @@ module.exports = function elasticsearchApi(client = {}, logger, _opConfig) {
|
|
|
696
758
|
return (_data) => {
|
|
697
759
|
const args = _data || data;
|
|
698
760
|
|
|
761
|
+
_awaitRetry(delay)
|
|
762
|
+
.then((newDelay) => {
|
|
763
|
+
delay = newDelay;
|
|
764
|
+
fn(args);
|
|
765
|
+
})
|
|
766
|
+
.catch(reject);
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* @returns {Promise<number>} the delayed time
|
|
772
|
+
*/
|
|
773
|
+
async function _awaitRetry(previousDelay = 0) {
|
|
774
|
+
return new Promise((resolve, reject) => {
|
|
699
775
|
waitForClient((elapsed) => {
|
|
700
|
-
delay = getBackoffDelay(
|
|
776
|
+
const delay = getBackoffDelay(previousDelay, 2, retryLimit, retryStart);
|
|
701
777
|
|
|
702
778
|
let timeoutMs = delay - elapsed;
|
|
703
779
|
if (timeoutMs < 1) timeoutMs = 1;
|
|
704
780
|
|
|
705
|
-
setTimeout(
|
|
706
|
-
fn(args);
|
|
707
|
-
}, timeoutMs);
|
|
781
|
+
setTimeout(resolve, timeoutMs, delay);
|
|
708
782
|
}, reject);
|
|
709
|
-
};
|
|
783
|
+
});
|
|
710
784
|
}
|
|
711
785
|
|
|
712
786
|
function _errorHandler(fn, data, reject, fnName = '->unknown()') {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terascope/elasticsearch-api",
|
|
3
3
|
"displayName": "Elasticsearch API",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.2",
|
|
5
5
|
"description": "Elasticsearch client api used across multiple services, handles retries and exponential backoff",
|
|
6
6
|
"homepage": "https://github.com/terascope/teraslice/tree/master/packages/elasticsearch-api#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"test:watch": "ts-scripts test --watch . --"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@terascope/utils": "^0.
|
|
21
|
+
"@terascope/utils": "^0.44.1",
|
|
22
22
|
"bluebird": "^3.7.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/elasticsearch": "^5.0.
|
|
25
|
+
"@types/elasticsearch": "^5.0.40"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": "^12.22.0 || >=14.17.0",
|
package/test/api-spec.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Promise = require('bluebird');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
debugLogger, cloneDeep, DataEntity, isEmpty
|
|
6
|
+
} = require('@terascope/utils');
|
|
5
7
|
const esApi = require('..');
|
|
6
8
|
|
|
7
9
|
describe('elasticsearch-api', () => {
|
|
@@ -189,15 +191,63 @@ describe('elasticsearch-api', () => {
|
|
|
189
191
|
|
|
190
192
|
function createBulkResponse(results) {
|
|
191
193
|
const response = { took: 22, errors: false, items: results };
|
|
192
|
-
if (bulkError) {
|
|
194
|
+
if (!isEmpty(bulkError)) {
|
|
193
195
|
response.errors = true;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
196
|
+
let i = -1;
|
|
197
|
+
response.items = results.body.flatMap((obj) => {
|
|
198
|
+
if (!obj.index && !obj.update && !obj.create && !obj.delete) {
|
|
199
|
+
// ignore the non-metadata objects
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
i++;
|
|
203
|
+
const [key, value] = Object.entries(obj)[0];
|
|
204
|
+
return [{
|
|
205
|
+
[key]: {
|
|
206
|
+
_index: value._index,
|
|
207
|
+
_type: value._type,
|
|
208
|
+
_id: String(i),
|
|
209
|
+
_version: 1,
|
|
210
|
+
result: `${key}d`,
|
|
211
|
+
error: { type: bulkError[i] || 'someType', reason: 'someReason' },
|
|
212
|
+
_shards: {
|
|
213
|
+
total: 2,
|
|
214
|
+
successful: 1,
|
|
215
|
+
failed: 0
|
|
216
|
+
},
|
|
217
|
+
status: 400,
|
|
218
|
+
_seq_no: 2,
|
|
219
|
+
_primary_term: 3
|
|
220
|
+
}
|
|
221
|
+
}];
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
response.errors = false;
|
|
225
|
+
let i = -1;
|
|
226
|
+
response.items = results.body.flatMap((obj) => {
|
|
227
|
+
if (!obj.index && !obj.update && !obj.create && !obj.delete) {
|
|
228
|
+
// ignore the non-metadata objects
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
i++;
|
|
233
|
+
const [key, value] = Object.entries(obj)[0];
|
|
234
|
+
return [{
|
|
235
|
+
[key]: {
|
|
236
|
+
_index: value._index,
|
|
237
|
+
_type: value._type,
|
|
238
|
+
_id: String(i),
|
|
239
|
+
_version: 1,
|
|
240
|
+
result: `${key}d`,
|
|
241
|
+
_shards: {
|
|
242
|
+
total: 2,
|
|
243
|
+
successful: 1,
|
|
244
|
+
failed: 0
|
|
245
|
+
},
|
|
246
|
+
status: 200,
|
|
247
|
+
_seq_no: 2,
|
|
248
|
+
_primary_term: 3
|
|
249
|
+
}
|
|
250
|
+
}];
|
|
201
251
|
});
|
|
202
252
|
}
|
|
203
253
|
return response;
|
|
@@ -323,45 +373,25 @@ describe('elasticsearch-api', () => {
|
|
|
323
373
|
expect(() => {
|
|
324
374
|
api = esApi(client, logger);
|
|
325
375
|
}).not.toThrow();
|
|
326
|
-
expect(api).toBeDefined();
|
|
327
376
|
expect(typeof api).toEqual('object');
|
|
328
|
-
expect(api.search).toBeDefined();
|
|
329
377
|
expect(typeof api.search).toEqual('function');
|
|
330
|
-
expect(api.count).toBeDefined();
|
|
331
378
|
expect(typeof api.count).toEqual('function');
|
|
332
|
-
expect(api.get).toBeDefined();
|
|
333
379
|
expect(typeof api.get).toEqual('function');
|
|
334
|
-
expect(api.index).toBeDefined();
|
|
335
380
|
expect(typeof api.index).toEqual('function');
|
|
336
|
-
expect(api.indexWithId).toBeDefined();
|
|
337
381
|
expect(typeof api.indexWithId).toEqual('function');
|
|
338
|
-
expect(api.create).toBeDefined();
|
|
339
382
|
expect(typeof api.create).toEqual('function');
|
|
340
|
-
expect(api.update).toBeDefined();
|
|
341
383
|
expect(typeof api.update).toEqual('function');
|
|
342
|
-
expect(api.remove).toBeDefined();
|
|
343
384
|
expect(typeof api.remove).toEqual('function');
|
|
344
|
-
expect(api.version).toBeDefined();
|
|
345
385
|
expect(typeof api.version).toEqual('function');
|
|
346
|
-
expect(api.putTemplate).toBeDefined();
|
|
347
386
|
expect(typeof api.putTemplate).toEqual('function');
|
|
348
|
-
expect(api.bulkSend).toBeDefined();
|
|
349
387
|
expect(typeof api.bulkSend).toEqual('function');
|
|
350
|
-
expect(api.nodeInfo).toBeDefined();
|
|
351
388
|
expect(typeof api.nodeInfo).toEqual('function');
|
|
352
|
-
expect(api.nodeStats).toBeDefined();
|
|
353
389
|
expect(typeof api.nodeStats).toEqual('function');
|
|
354
|
-
expect(api.buildQuery).toBeDefined();
|
|
355
390
|
expect(typeof api.buildQuery).toEqual('function');
|
|
356
|
-
expect(api.indexExists).toBeDefined();
|
|
357
391
|
expect(typeof api.indexExists).toEqual('function');
|
|
358
|
-
expect(api.indexCreate).toBeDefined();
|
|
359
392
|
expect(typeof api.indexCreate).toEqual('function');
|
|
360
|
-
expect(api.indexRefresh).toBeDefined();
|
|
361
393
|
expect(typeof api.indexRefresh).toEqual('function');
|
|
362
|
-
expect(api.indexRecovery).toBeDefined();
|
|
363
394
|
expect(typeof api.indexRecovery).toEqual('function');
|
|
364
|
-
expect(api.indexSetup).toBeDefined();
|
|
365
395
|
expect(typeof api.indexSetup).toEqual('function');
|
|
366
396
|
});
|
|
367
397
|
|
|
@@ -717,30 +747,48 @@ describe('elasticsearch-api', () => {
|
|
|
717
747
|
|
|
718
748
|
it('can call bulkSend', async () => {
|
|
719
749
|
const api = esApi(client, logger);
|
|
720
|
-
const myBulkData = [
|
|
721
|
-
{ index: { _index: 'some_index', _type: 'events', _id: 1 } },
|
|
722
|
-
{ title: 'foo' },
|
|
723
|
-
{ delete: { _index: 'some_index', _type: 'events', _id: 5 } }
|
|
724
|
-
];
|
|
725
750
|
|
|
726
|
-
const
|
|
727
|
-
|
|
751
|
+
const result = await api.bulkSend([
|
|
752
|
+
{
|
|
753
|
+
action: {
|
|
754
|
+
index: { _index: 'some_index', _type: 'events', _id: 1 }
|
|
755
|
+
},
|
|
756
|
+
data: { title: 'foo' }
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
action: {
|
|
760
|
+
delete: { _index: 'some_index', _type: 'events', _id: 5 }
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
]);
|
|
764
|
+
expect(bulkData).toEqual({
|
|
765
|
+
body: [
|
|
766
|
+
{ index: { _index: 'some_index', _type: 'events', _id: 1 } },
|
|
767
|
+
{ title: 'foo' },
|
|
768
|
+
{ delete: { _index: 'some_index', _type: 'events', _id: 5 } }
|
|
769
|
+
]
|
|
770
|
+
});
|
|
771
|
+
return expect(result).toBe(2);
|
|
728
772
|
});
|
|
729
773
|
|
|
730
|
-
it('can remove type from
|
|
774
|
+
it('can remove type from bulkSend', async () => {
|
|
731
775
|
const es7client = cloneDeep(client);
|
|
732
776
|
|
|
733
777
|
es7client.transport._config = { apiVersion: '7.0' };
|
|
734
778
|
|
|
735
779
|
const api = esApi(es7client, logger);
|
|
736
780
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
781
|
+
await api.bulkSend([{
|
|
782
|
+
action: {
|
|
783
|
+
index: { _index: 'some_index', _type: 'events', _id: 1 }
|
|
784
|
+
},
|
|
785
|
+
data: { title: 'foo' }
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
action: {
|
|
789
|
+
delete: { _index: 'some_index', _type: 'events', _id: 5 }
|
|
790
|
+
}
|
|
791
|
+
}]);
|
|
744
792
|
expect(bulkData).toEqual({
|
|
745
793
|
body: [
|
|
746
794
|
{ index: { _index: 'some_index', _id: 1 } },
|
|
@@ -750,20 +798,24 @@ describe('elasticsearch-api', () => {
|
|
|
750
798
|
});
|
|
751
799
|
});
|
|
752
800
|
|
|
753
|
-
it('will not remove _type from record in a
|
|
801
|
+
it('will not remove _type from record in a bulkSend', async () => {
|
|
754
802
|
const es7client = cloneDeep(client);
|
|
755
803
|
|
|
756
804
|
es7client.transport._config = { apiVersion: '7.0' };
|
|
757
805
|
|
|
758
806
|
const api = esApi(es7client, logger);
|
|
759
807
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
808
|
+
await api.bulkSend([{
|
|
809
|
+
action: {
|
|
810
|
+
delete: { _index: 'some_index', _type: 'events', _id: 5 }
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
action: {
|
|
815
|
+
index: { _index: 'some_index', _type: 'events', _id: 1 }
|
|
816
|
+
},
|
|
817
|
+
data: { title: 'foo', _type: 'doc', name: 'joe' }
|
|
818
|
+
}]);
|
|
767
819
|
expect(bulkData).toEqual({
|
|
768
820
|
body: [
|
|
769
821
|
{ delete: { _index: 'some_index', _id: 5 } },
|
|
@@ -773,20 +825,25 @@ describe('elasticsearch-api', () => {
|
|
|
773
825
|
});
|
|
774
826
|
});
|
|
775
827
|
|
|
776
|
-
it('will not err if no _type in es7
|
|
828
|
+
it('will not err if no _type in es7 bulkSend request metadata', async () => {
|
|
777
829
|
const es7client = cloneDeep(client);
|
|
778
830
|
|
|
779
831
|
es7client.transport._config = { apiVersion: '7.0' };
|
|
780
832
|
|
|
781
833
|
const api = esApi(es7client, logger);
|
|
782
834
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
835
|
+
await api.bulkSend([{
|
|
836
|
+
action: {
|
|
837
|
+
delete: { _index: 'some_index', _type: 'events', _id: 5 }
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
action: {
|
|
842
|
+
index: { _index: 'some_index', _type: 'events', _id: 1 }
|
|
843
|
+
},
|
|
844
|
+
data: { title: 'foo', _type: 'doc', name: 'joe' }
|
|
845
|
+
}]);
|
|
788
846
|
|
|
789
|
-
await api.bulkSend(myBulkData);
|
|
790
847
|
expect(bulkData).toEqual({
|
|
791
848
|
body: [
|
|
792
849
|
{ delete: { _index: 'some_index', _id: 5 } },
|
|
@@ -798,36 +855,35 @@ describe('elasticsearch-api', () => {
|
|
|
798
855
|
|
|
799
856
|
it('can call bulkSend with errors', async () => {
|
|
800
857
|
const api = esApi(client, logger);
|
|
801
|
-
const myBulkData = [
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
858
|
+
const myBulkData = [{
|
|
859
|
+
action: {
|
|
860
|
+
index: { _index: 'some_index', _type: 'events', _id: 1 }
|
|
861
|
+
},
|
|
862
|
+
data: { title: 'foo' }
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
action: {
|
|
866
|
+
delete: { _index: 'some_index', _type: 'events', _id: 5 }
|
|
867
|
+
}
|
|
868
|
+
}];
|
|
806
869
|
|
|
807
870
|
bulkError = [
|
|
808
871
|
'es_rejected_execution_exception',
|
|
809
872
|
'es_rejected_execution_exception',
|
|
810
|
-
'es_rejected_execution_exception'
|
|
811
873
|
];
|
|
812
874
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
})
|
|
818
|
-
]);
|
|
875
|
+
waitFor(20, () => {
|
|
876
|
+
bulkError = false;
|
|
877
|
+
});
|
|
878
|
+
const result = await api.bulkSend(myBulkData);
|
|
819
879
|
|
|
820
|
-
expect(
|
|
821
|
-
bulkError = ['some_thing_else', 'some_thing_else', 'some_thing_else'];
|
|
880
|
+
expect(result).toBe(2);
|
|
822
881
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
})
|
|
829
|
-
])
|
|
830
|
-
).rejects.toThrow(/some_thing_else--someReason/);
|
|
882
|
+
bulkError = ['some_thing_else', 'some_thing_else'];
|
|
883
|
+
|
|
884
|
+
await expect(
|
|
885
|
+
api.bulkSend(myBulkData)
|
|
886
|
+
).rejects.toThrow('bulk send error: some_thing_else--someReason');
|
|
831
887
|
});
|
|
832
888
|
|
|
833
889
|
it('can call buildQuery for geo queries', () => {
|
package/types/index.d.ts
CHANGED
|
@@ -28,7 +28,12 @@ declare namespace elasticsearchAPI {
|
|
|
28
28
|
remove: (query: es.DeleteDocumentParams) => Promise<es.DeleteDocumentResponse>;
|
|
29
29
|
version: () => Promise<boolean>;
|
|
30
30
|
putTemplate: (template: any, name: string) => Promise<any>;
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The new and improved bulk send with proper retry support
|
|
33
|
+
*
|
|
34
|
+
* @returns the number of affected rows
|
|
35
|
+
*/
|
|
36
|
+
bulkSend: (data: BulkRecord[]) => Promise<number>;
|
|
32
37
|
nodeInfo: (query: any) => Promise<any>;
|
|
33
38
|
nodeStats: (query: any) => Promise<any>;
|
|
34
39
|
buildQuery: (opConfig: Config, msg: any) => es.SearchParams;
|
|
@@ -41,4 +46,32 @@ declare namespace elasticsearchAPI {
|
|
|
41
46
|
validateGeoParameters: (opConfig: any) => any;
|
|
42
47
|
getESVersion: () => number;
|
|
43
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* This is used for improved bulk sending function
|
|
52
|
+
*/
|
|
53
|
+
export interface BulkRecord {
|
|
54
|
+
action: AnyBulkAction;
|
|
55
|
+
data?: UpdateConfig | DataEntity;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* This is used for improved bulk sending function
|
|
60
|
+
*/
|
|
61
|
+
export interface AnyBulkAction {
|
|
62
|
+
update?: Partial<BulkActionMetadata>;
|
|
63
|
+
index?: Partial<BulkActionMetadata>;
|
|
64
|
+
create?: Partial<BulkActionMetadata>;
|
|
65
|
+
delete?: Partial<BulkActionMetadata>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* This is used for improved bulk sending function
|
|
70
|
+
*/
|
|
71
|
+
export interface BulkActionMetadata {
|
|
72
|
+
_index: string;
|
|
73
|
+
_type: string;
|
|
74
|
+
_id: string | number;
|
|
75
|
+
retry_on_conflict?: number;
|
|
76
|
+
}
|
|
44
77
|
}
|