@@ -128,6 +128,7 @@ export interface BulkHelperOptions<TDocument = unknown> extends T.BulkRequest {
128
128
retries ?: number
129
129
wait ?: number
130
130
onDrop ?: ( doc : OnDropDocument < TDocument > ) => void
131
+ onSuccess ?: ( doc : OnSuccessDocument ) => void
131
132
}
132
133
133
134
export interface BulkHelper < T > extends Promise < BulkStats > {
@@ -397,7 +398,7 @@ export default class Helpers {
397
398
clearTimeout ( timeoutRef )
398
399
}
399
400
400
- // In some cases the previos http call does not have finished,
401
+ // In some cases the previous http call does not have finished,
401
402
// or we didn't reach the flush bytes threshold, so we force one last operation.
402
403
if ( loadedOperations > 0 ) {
403
404
const send = await semaphore ( )
@@ -433,8 +434,8 @@ export default class Helpers {
433
434
// to guarantee that no more than the number of operations
434
435
// allowed to run at the same time are executed.
435
436
// It returns a semaphore function which resolves in the next tick
436
- // if we didn't reach the maximim concurrency yet, otherwise it returns
437
- // a promise that resolves as soon as one of the running request has finshed .
437
+ // if we didn't reach the maximum concurrency yet, otherwise it returns
438
+ // a promise that resolves as soon as one of the running requests has finished .
438
439
// The semaphore function resolves a send function, which will be used
439
440
// to send the actual msearch request.
440
441
// It also returns a finish function, which returns a promise that is resolved
@@ -566,6 +567,9 @@ export default class Helpers {
566
567
retries = this [ kMaxRetries ] ,
567
568
wait = 5000 ,
568
569
onDrop = noop ,
570
+ // onSuccess does not default to noop, to avoid the performance hit
571
+ // of deserializing every document in the bulk request
572
+ onSuccess,
569
573
...bulkOptions
570
574
} = options
571
575
@@ -638,7 +642,7 @@ export default class Helpers {
638
642
let chunkBytes = 0
639
643
timeoutRef = setTimeout ( onFlushTimeout , flushInterval ) // eslint-disable-line
640
644
641
- // @ts -expect-error datasoruce is an iterable
645
+ // @ts -expect-error datasource is an iterable
642
646
for await ( const chunk of datasource ) {
643
647
if ( shouldAbort ) break
644
648
timeoutRef . refresh ( )
@@ -679,7 +683,7 @@ export default class Helpers {
679
683
}
680
684
681
685
clearTimeout ( timeoutRef )
682
- // In some cases the previos http call does not have finished,
686
+ // In some cases the previous http call has not finished,
683
687
// or we didn't reach the flush bytes threshold, so we force one last operation.
684
688
if ( ! shouldAbort && chunkBytes > 0 ) {
685
689
const send = await semaphore ( )
@@ -715,8 +719,8 @@ export default class Helpers {
715
719
// to guarantee that no more than the number of operations
716
720
// allowed to run at the same time are executed.
717
721
// It returns a semaphore function which resolves in the next tick
718
- // if we didn't reach the maximim concurrency yet, otherwise it returns
719
- // a promise that resolves as soon as one of the running request has finshed .
722
+ // if we didn't reach the maximum concurrency yet, otherwise it returns
723
+ // a promise that resolves as soon as one of the running requests has finished .
720
724
// The semaphore function resolves a send function, which will be used
721
725
// to send the actual bulk request.
722
726
// It also returns a finish function, which returns a promise that is resolved
@@ -823,57 +827,93 @@ export default class Helpers {
823
827
callback ( )
824
828
}
825
829
830
+ /**
831
+ * Zips bulk response items (the action's result) with the original document body.
832
+ * The raw string version of action and document lines are also included.
833
+ */
834
+ function zipBulkResults ( responseItems : BulkResponseItem [ ] , bulkBody : string [ ] ) : ZippedResult [ ] {
835
+ const zipped = [ ]
836
+ let indexSlice = 0
837
+ for ( let i = 0 , len = responseItems . length ; i < len ; i ++ ) {
838
+ const result = responseItems [ i ]
839
+ const operation = Object . keys ( result ) [ 0 ]
840
+ let zipResult
841
+
842
+ if ( operation === 'delete' ) {
843
+ zipResult = {
844
+ result,
845
+ raw : { action : bulkBody [ indexSlice ] }
846
+ }
847
+ indexSlice += 1
848
+ } else {
849
+ const document = bulkBody [ indexSlice + 1 ]
850
+ zipResult = {
851
+ result,
852
+ raw : { action : bulkBody [ indexSlice ] , document } ,
853
+ // this is a function so that deserialization is only done when needed
854
+ // to avoid a performance hit
855
+ document : ( ) => serializer . deserialize ( document )
856
+ }
857
+ indexSlice += 2
858
+ }
859
+
860
+ zipped . push ( zipResult as ZippedResult )
861
+ }
862
+
863
+ return zipped
864
+ }
865
+
826
866
function tryBulk ( bulkBody : string [ ] , callback : ( err : Error | null , bulkBody : string [ ] ) => void ) : void {
827
867
if ( shouldAbort ) return callback ( null , [ ] )
828
868
client . bulk ( Object . assign ( { } , bulkOptions , { body : bulkBody } ) , reqOptions as TransportRequestOptionsWithMeta )
829
869
. then ( response => {
830
870
const result = response . body
871
+ const results = zipBulkResults ( result . items , bulkBody )
872
+
831
873
if ( ! result . errors ) {
832
874
stats . successful += result . items . length
833
- for ( const item of result . items ) {
834
- if ( item . update ?. result === 'noop' ) {
875
+ for ( const item of results ) {
876
+ const { result, document = noop } = item
877
+ if ( result . update ?. result === 'noop' ) {
835
878
stats . noop ++
836
879
}
880
+ if ( onSuccess != null ) onSuccess ( { result, document : document ( ) } )
837
881
}
838
882
return callback ( null , [ ] )
839
883
}
840
884
const retry = [ ]
841
- const { items } = result
842
- let indexSlice = 0
843
- for ( let i = 0 , len = items . length ; i < len ; i ++ ) {
844
- const action = items [ i ]
845
- const operation = Object . keys ( action ) [ 0 ]
885
+ for ( const item of results ) {
886
+ const { result, raw, document = noop } = item
887
+ const operation = Object . keys ( result ) [ 0 ]
846
888
// @ts -expect-error
847
- const responseItem = action [ operation as keyof T . BulkResponseItemContainer ]
889
+ const responseItem = result [ operation as keyof T . BulkResponseItemContainer ]
848
890
assert ( responseItem !== undefined , 'The responseItem is undefined, please file a bug report' )
849
891
850
892
if ( responseItem . status >= 400 ) {
851
893
// 429 is the only status code where we might want to retry
852
894
// a document, because it was not an error in the document itself,
853
- // but the ES node were handling too many operations.
895
+ // but the ES node was handling too many operations.
854
896
if ( responseItem . status === 429 ) {
855
- retry . push ( bulkBody [ indexSlice ] )
897
+ retry . push ( raw . action )
856
898
/* istanbul ignore next */
857
899
if ( operation !== 'delete' ) {
858
- retry . push ( bulkBody [ indexSlice + 1 ] )
900
+ retry . push ( raw . document ?? '' )
859
901
}
860
902
} else {
861
903
onDrop ( {
862
904
status : responseItem . status ,
863
905
error : responseItem . error ?? null ,
864
- operation : serializer . deserialize ( bulkBody [ indexSlice ] ) ,
906
+ operation : serializer . deserialize ( raw . action ) ,
865
907
// @ts -expect-error
866
- document : operation !== 'delete'
867
- ? serializer . deserialize ( bulkBody [ indexSlice + 1 ] )
868
- : null ,
908
+ document : document ( ) ,
869
909
retried : isRetrying
870
910
} )
871
911
stats . failed += 1
872
912
}
873
913
} else {
874
914
stats . successful += 1
915
+ if ( onSuccess != null ) onSuccess ( { result, document : document ( ) } )
875
916
}
876
- operation === 'delete' ? indexSlice += 1 : indexSlice += 2
877
917
}
878
918
callback ( null , retry )
879
919
} )
0 commit comments