mongodb 4.7.0 → 4.9.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 (127) hide show
  1. package/lib/bson.js +4 -2
  2. package/lib/bson.js.map +1 -1
  3. package/lib/bulk/common.js +1 -0
  4. package/lib/bulk/common.js.map +1 -1
  5. package/lib/change_stream.js +136 -271
  6. package/lib/change_stream.js.map +1 -1
  7. package/lib/cmap/command_monitoring_events.js +2 -32
  8. package/lib/cmap/command_monitoring_events.js.map +1 -1
  9. package/lib/cmap/commands.js +1 -153
  10. package/lib/cmap/commands.js.map +1 -1
  11. package/lib/cmap/connect.js +3 -6
  12. package/lib/cmap/connect.js.map +1 -1
  13. package/lib/cmap/connection.js +21 -84
  14. package/lib/cmap/connection.js.map +1 -1
  15. package/lib/cmap/connection_pool.js +196 -170
  16. package/lib/cmap/connection_pool.js.map +1 -1
  17. package/lib/cmap/message_stream.js.map +1 -1
  18. package/lib/cmap/wire_protocol/compression.js +2 -6
  19. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  20. package/lib/cmap/wire_protocol/constants.js +1 -3
  21. package/lib/cmap/wire_protocol/constants.js.map +1 -1
  22. package/lib/collection.js +24 -9
  23. package/lib/collection.js.map +1 -1
  24. package/lib/connection_string.js.map +1 -1
  25. package/lib/cursor/abstract_cursor.js +62 -75
  26. package/lib/cursor/abstract_cursor.js.map +1 -1
  27. package/lib/cursor/change_stream_cursor.js +115 -0
  28. package/lib/cursor/change_stream_cursor.js.map +1 -0
  29. package/lib/cursor/list_collections_cursor.js +37 -0
  30. package/lib/cursor/list_collections_cursor.js.map +1 -0
  31. package/lib/cursor/list_indexes_cursor.js +36 -0
  32. package/lib/cursor/list_indexes_cursor.js.map +1 -0
  33. package/lib/db.js +2 -2
  34. package/lib/db.js.map +1 -1
  35. package/lib/deps.js.map +1 -1
  36. package/lib/encrypter.js +3 -13
  37. package/lib/encrypter.js.map +1 -1
  38. package/lib/index.js +28 -21
  39. package/lib/index.js.map +1 -1
  40. package/lib/mongo_client.js +62 -21
  41. package/lib/mongo_client.js.map +1 -1
  42. package/lib/mongo_types.js.map +1 -1
  43. package/lib/operations/common_functions.js.map +1 -1
  44. package/lib/operations/create_collection.js.map +1 -1
  45. package/lib/operations/distinct.js +5 -5
  46. package/lib/operations/distinct.js.map +1 -1
  47. package/lib/operations/estimated_document_count.js +5 -0
  48. package/lib/operations/estimated_document_count.js.map +1 -1
  49. package/lib/operations/execute_operation.js +17 -8
  50. package/lib/operations/execute_operation.js.map +1 -1
  51. package/lib/operations/find.js +3 -0
  52. package/lib/operations/find.js.map +1 -1
  53. package/lib/operations/get_more.js +32 -7
  54. package/lib/operations/get_more.js.map +1 -1
  55. package/lib/operations/indexes.js +39 -65
  56. package/lib/operations/indexes.js.map +1 -1
  57. package/lib/operations/kill_cursors.js +32 -0
  58. package/lib/operations/kill_cursors.js.map +1 -0
  59. package/lib/operations/list_collections.js +1 -33
  60. package/lib/operations/list_collections.js.map +1 -1
  61. package/lib/operations/operation.js +4 -1
  62. package/lib/operations/operation.js.map +1 -1
  63. package/lib/read_preference.js.map +1 -1
  64. package/lib/sdam/common.js +2 -1
  65. package/lib/sdam/common.js.map +1 -1
  66. package/lib/sdam/monitor.js +2 -1
  67. package/lib/sdam/monitor.js.map +1 -1
  68. package/lib/sdam/server.js +1 -52
  69. package/lib/sdam/server.js.map +1 -1
  70. package/lib/sdam/server_description.js +51 -58
  71. package/lib/sdam/server_description.js.map +1 -1
  72. package/lib/sdam/srv_polling.js +2 -2
  73. package/lib/sdam/srv_polling.js.map +1 -1
  74. package/lib/sdam/topology.js +28 -67
  75. package/lib/sdam/topology.js.map +1 -1
  76. package/lib/sdam/topology_description.js +24 -42
  77. package/lib/sdam/topology_description.js.map +1 -1
  78. package/lib/sessions.js +29 -31
  79. package/lib/sessions.js.map +1 -1
  80. package/lib/utils.js +65 -70
  81. package/lib/utils.js.map +1 -1
  82. package/mongodb.d.ts +136 -73
  83. package/package.json +23 -22
  84. package/src/bson.ts +4 -0
  85. package/src/bulk/common.ts +1 -0
  86. package/src/change_stream.ts +147 -373
  87. package/src/cmap/command_monitoring_events.ts +3 -37
  88. package/src/cmap/commands.ts +2 -190
  89. package/src/cmap/connect.ts +20 -25
  90. package/src/cmap/connection.ts +27 -139
  91. package/src/cmap/connection_pool.ts +208 -169
  92. package/src/cmap/message_stream.ts +2 -3
  93. package/src/cmap/wire_protocol/compression.ts +8 -6
  94. package/src/cmap/wire_protocol/constants.ts +0 -2
  95. package/src/collection.ts +27 -13
  96. package/src/connection_string.ts +1 -1
  97. package/src/cursor/abstract_cursor.ts +98 -87
  98. package/src/cursor/change_stream_cursor.ts +194 -0
  99. package/src/cursor/list_collections_cursor.ts +52 -0
  100. package/src/cursor/list_indexes_cursor.ts +41 -0
  101. package/src/db.ts +2 -5
  102. package/src/deps.ts +13 -22
  103. package/src/encrypter.ts +4 -14
  104. package/src/index.ts +13 -9
  105. package/src/mongo_client.ts +102 -33
  106. package/src/mongo_types.ts +81 -57
  107. package/src/operations/common_functions.ts +1 -1
  108. package/src/operations/create_collection.ts +1 -2
  109. package/src/operations/distinct.ts +7 -9
  110. package/src/operations/estimated_document_count.ts +6 -0
  111. package/src/operations/execute_operation.ts +17 -8
  112. package/src/operations/find.ts +9 -0
  113. package/src/operations/get_more.ts +56 -13
  114. package/src/operations/indexes.ts +52 -89
  115. package/src/operations/kill_cursors.ts +53 -0
  116. package/src/operations/list_collections.ts +0 -43
  117. package/src/operations/operation.ts +5 -1
  118. package/src/read_preference.ts +5 -9
  119. package/src/sdam/common.ts +2 -0
  120. package/src/sdam/monitor.ts +2 -1
  121. package/src/sdam/server.ts +4 -89
  122. package/src/sdam/server_description.ts +70 -80
  123. package/src/sdam/srv_polling.ts +1 -1
  124. package/src/sdam/topology.ts +37 -103
  125. package/src/sdam/topology_description.ts +44 -68
  126. package/src/sessions.ts +32 -39
  127. package/src/utils.ts +78 -75
@@ -1,16 +1,11 @@
1
- import Denque = require('denque');
2
1
  import type { Readable } from 'stream';
3
- import { setTimeout } from 'timers';
2
+ import { promisify } from 'util';
4
3
 
5
- import type { Binary, Document, Long, Timestamp } from './bson';
4
+ import type { Binary, Document, Timestamp } from './bson';
6
5
  import { Collection } from './collection';
7
6
  import { CHANGE, CLOSE, END, ERROR, INIT, MORE, RESPONSE, RESUME_TOKEN_CHANGED } from './constants';
8
- import {
9
- AbstractCursor,
10
- AbstractCursorEvents,
11
- AbstractCursorOptions,
12
- CursorStreamOptions
13
- } from './cursor/abstract_cursor';
7
+ import type { AbstractCursorEvents, CursorStreamOptions } from './cursor/abstract_cursor';
8
+ import { ChangeStreamCursor, ChangeStreamCursorOptions } from './cursor/change_stream_cursor';
14
9
  import { Db } from './db';
15
10
  import {
16
11
  AnyError,
@@ -20,26 +15,13 @@ import {
20
15
  MongoRuntimeError
21
16
  } from './error';
22
17
  import { MongoClient } from './mongo_client';
23
- import { InferIdType, TODO_NODE_3286, TypedEventEmitter } from './mongo_types';
24
- import { AggregateOperation, AggregateOptions } from './operations/aggregate';
18
+ import { InferIdType, TypedEventEmitter } from './mongo_types';
19
+ import type { AggregateOptions } from './operations/aggregate';
25
20
  import type { CollationOptions, OperationParent } from './operations/command';
26
- import { executeOperation, ExecutionResult } from './operations/execute_operation';
27
21
  import type { ReadPreference } from './read_preference';
28
- import type { Topology } from './sdam/topology';
29
- import type { ClientSession, ServerSessionId } from './sessions';
30
- import {
31
- calculateDurationInMs,
32
- Callback,
33
- filterOptions,
34
- getTopology,
35
- maxWireVersion,
36
- maybePromise,
37
- MongoDBNamespace,
38
- now
39
- } from './utils';
22
+ import type { ServerSessionId } from './sessions';
23
+ import { Callback, filterOptions, getTopology, maybePromise, MongoDBNamespace } from './utils';
40
24
 
41
- /** @internal */
42
- const kResumeQueue = Symbol('resumeQueue');
43
25
  /** @internal */
44
26
  const kCursorStream = Symbol('cursorStream');
45
27
  /** @internal */
@@ -62,19 +44,10 @@ const CHANGE_DOMAIN_TYPES = {
62
44
  CLUSTER: Symbol('Cluster')
63
45
  };
64
46
 
65
- interface TopologyWaitOptions {
66
- start?: number;
67
- timeout?: number;
68
- readPreference?: ReadPreference;
69
- }
70
-
71
- const SELECTION_TIMEOUT = 30000;
72
-
73
47
  const CHANGE_STREAM_EVENTS = [RESUME_TOKEN_CHANGED, END, CLOSE];
74
48
 
75
49
  const NO_RESUME_TOKEN_ERROR =
76
50
  'A change stream document has been received that lacks a resume token (_id).';
77
- const NO_CURSOR_ERROR = 'ChangeStream has no cursor';
78
51
  const CHANGESTREAM_CLOSED_ERROR = 'ChangeStream is closed';
79
52
 
80
53
  /**
@@ -111,18 +84,6 @@ export interface PipeOptions {
111
84
  end?: boolean;
112
85
  }
113
86
 
114
- /** @internal */
115
- export type ChangeStreamAggregateRawResult<TChange> = {
116
- $clusterTime: { clusterTime: Timestamp };
117
- cursor: {
118
- postBatchResumeToken: ResumeToken;
119
- ns: string;
120
- id: number | Long;
121
- } & ({ firstBatch: TChange[] } | { nextBatch: TChange[] });
122
- ok: 1;
123
- operationTime: Timestamp;
124
- };
125
-
126
87
  /**
127
88
  * Options that can be passed to a ChangeStream. Note that startAfter, resumeAfter, and startAtOperationTime are all mutually exclusive, and the server will error if more than one is specified.
128
89
  * @public
@@ -565,11 +526,9 @@ export class ChangeStream<
565
526
  namespace: MongoDBNamespace;
566
527
  type: symbol;
567
528
  /** @internal */
568
- cursor: ChangeStreamCursor<TSchema, TChange> | undefined;
529
+ cursor: ChangeStreamCursor<TSchema, TChange>;
569
530
  streamOptions?: CursorStreamOptions;
570
531
  /** @internal */
571
- [kResumeQueue]: Denque<Callback<ChangeStreamCursor<TSchema, TChange>>>;
572
- /** @internal */
573
532
  [kCursorStream]?: Readable & AsyncIterable<TChange>;
574
533
  /** @internal */
575
534
  [kClosed]: boolean;
@@ -635,8 +594,6 @@ export class ChangeStream<
635
594
  this.options.readPreference = parent.readPreference;
636
595
  }
637
596
 
638
- this[kResumeQueue] = new Denque();
639
-
640
597
  // Create contained Change Stream cursor
641
598
  this.cursor = this._createChangeStreamCursor(options);
642
599
 
@@ -672,11 +629,28 @@ export class ChangeStream<
672
629
  hasNext(callback: Callback<boolean>): void;
673
630
  hasNext(callback?: Callback): Promise<boolean> | void {
674
631
  this._setIsIterator();
675
- return maybePromise(callback, cb => {
676
- this._getCursor((err, cursor) => {
677
- if (err || !cursor) return cb(err); // failed to resume, raise an error
678
- cursor.hasNext(cb);
679
- });
632
+ // TOOD(NODE-4319): Add eslint rule preventing accidental variable shadowing
633
+ // Shadowing is intentional here. We want to override the `callback` variable
634
+ // from the outer scope so that the inner scope doesn't accidentally call the wrong callback.
635
+ return maybePromise(callback, callback => {
636
+ (async () => {
637
+ try {
638
+ const hasNext = await this.cursor.hasNext();
639
+ return hasNext;
640
+ } catch (error) {
641
+ try {
642
+ await this._processErrorIteratorMode(error);
643
+ const hasNext = await this.cursor.hasNext();
644
+ return hasNext;
645
+ } catch (error) {
646
+ await this.close().catch(err => err);
647
+ throw error;
648
+ }
649
+ }
650
+ })().then(
651
+ hasNext => callback(undefined, hasNext),
652
+ error => callback(error)
653
+ );
680
654
  });
681
655
  }
682
656
 
@@ -685,39 +659,80 @@ export class ChangeStream<
685
659
  next(callback: Callback<TChange>): void;
686
660
  next(callback?: Callback<TChange>): Promise<TChange> | void {
687
661
  this._setIsIterator();
688
- return maybePromise(callback, cb => {
689
- this._getCursor((err, cursor) => {
690
- if (err || !cursor) return cb(err); // failed to resume, raise an error
691
- cursor.next((error, change) => {
692
- if (error) {
693
- this[kResumeQueue].push(() => this.next(cb));
694
- this._processError(error, cb);
695
- return;
662
+ // TOOD(NODE-4319): Add eslint rule preventing accidental variable shadowing
663
+ // Shadowing is intentional here. We want to override the `callback` variable
664
+ // from the outer scope so that the inner scope doesn't accidentally call the wrong callback.
665
+ return maybePromise(callback, callback => {
666
+ (async () => {
667
+ try {
668
+ const change = await this.cursor.next();
669
+ const processedChange = this._processChange(change ?? null);
670
+ return processedChange;
671
+ } catch (error) {
672
+ try {
673
+ await this._processErrorIteratorMode(error);
674
+ const change = await this.cursor.next();
675
+ const processedChange = this._processChange(change ?? null);
676
+ return processedChange;
677
+ } catch (error) {
678
+ await this.close().catch(err => err);
679
+ throw error;
696
680
  }
697
- this._processNewChange(change ?? null, cb);
698
- });
699
- });
681
+ }
682
+ })().then(
683
+ change => callback(undefined, change),
684
+ error => callback(error)
685
+ );
686
+ });
687
+ }
688
+
689
+ /**
690
+ * Try to get the next available document from the Change Stream's cursor or `null` if an empty batch is returned
691
+ */
692
+ tryNext(): Promise<Document | null>;
693
+ tryNext(callback: Callback<Document | null>): void;
694
+ tryNext(callback?: Callback<Document | null>): Promise<Document | null> | void {
695
+ this._setIsIterator();
696
+ // TOOD(NODE-4319): Add eslint rule preventing accidental variable shadowing
697
+ // Shadowing is intentional here. We want to override the `callback` variable
698
+ // from the outer scope so that the inner scope doesn't accidentally call the wrong callback.
699
+ return maybePromise(callback, callback => {
700
+ (async () => {
701
+ try {
702
+ const change = await this.cursor.tryNext();
703
+ return change ?? null;
704
+ } catch (error) {
705
+ try {
706
+ await this._processErrorIteratorMode(error);
707
+ const change = await this.cursor.tryNext();
708
+ return change ?? null;
709
+ } catch (error) {
710
+ await this.close().catch(err => err);
711
+ throw error;
712
+ }
713
+ }
714
+ })().then(
715
+ change => callback(undefined, change),
716
+ error => callback(error)
717
+ );
700
718
  });
701
719
  }
702
720
 
703
721
  /** Is the cursor closed */
704
722
  get closed(): boolean {
705
- return this[kClosed] || (this.cursor?.closed ?? false);
723
+ return this[kClosed] || this.cursor.closed;
706
724
  }
707
725
 
708
726
  /** Close the Change Stream */
727
+ close(): Promise<void>;
728
+ close(callback: Callback): void;
709
729
  close(callback?: Callback): Promise<void> | void {
710
730
  this[kClosed] = true;
711
731
 
712
732
  return maybePromise(callback, cb => {
713
- if (!this.cursor) {
714
- return cb();
715
- }
716
-
717
733
  const cursor = this.cursor;
718
734
  return cursor.close(err => {
719
735
  this._endStream();
720
- this.cursor = undefined;
721
736
  return cb(err);
722
737
  });
723
738
  });
@@ -725,29 +740,21 @@ export class ChangeStream<
725
740
 
726
741
  /**
727
742
  * Return a modified Readable stream including a possible transform method.
728
- * @throws MongoDriverError if this.cursor is undefined
743
+ *
744
+ * NOTE: When using a Stream to process change stream events, the stream will
745
+ * NOT automatically resume in the case a resumable error is encountered.
746
+ *
747
+ * @throws MongoChangeStreamError if the underlying cursor or the change stream is closed
729
748
  */
730
749
  stream(options?: CursorStreamOptions): Readable & AsyncIterable<TChange> {
750
+ if (this.closed) {
751
+ throw new MongoChangeStreamError(CHANGESTREAM_CLOSED_ERROR);
752
+ }
753
+
731
754
  this.streamOptions = options;
732
- if (!this.cursor) throw new MongoChangeStreamError(NO_CURSOR_ERROR);
733
755
  return this.cursor.stream(options);
734
756
  }
735
757
 
736
- /**
737
- * Try to get the next available document from the Change Stream's cursor or `null` if an empty batch is returned
738
- */
739
- tryNext(): Promise<Document | null>;
740
- tryNext(callback: Callback<Document | null>): void;
741
- tryNext(callback?: Callback<Document | null>): Promise<Document | null> | void {
742
- this._setIsIterator();
743
- return maybePromise(callback, cb => {
744
- this._getCursor((err, cursor) => {
745
- if (err || !cursor) return cb(err); // failed to resume, raise an error
746
- return cursor.tryNext(cb);
747
- });
748
- });
749
- }
750
-
751
758
  /** @internal */
752
759
  private _setIsEmitter(): void {
753
760
  if (this[kMode] === 'iterator') {
@@ -817,43 +824,13 @@ export class ChangeStream<
817
824
  return changeStreamCursor;
818
825
  }
819
826
 
820
- /**
821
- * This method performs a basic server selection loop, satisfying the requirements of
822
- * ChangeStream resumability until the new SDAM layer can be used.
823
- * @internal
824
- */
825
- private _waitForTopologyConnected(
826
- topology: Topology,
827
- options: TopologyWaitOptions,
828
- callback: Callback
829
- ) {
830
- setTimeout(() => {
831
- if (options && options.start == null) {
832
- options.start = now();
833
- }
834
-
835
- const start = options.start || now();
836
- const timeout = options.timeout || SELECTION_TIMEOUT;
837
- if (topology.isConnected()) {
838
- return callback();
839
- }
840
-
841
- if (calculateDurationInMs(start) > timeout) {
842
- // TODO(NODE-3497): Replace with MongoNetworkTimeoutError
843
- return callback(new MongoRuntimeError('Timed out waiting for connection'));
844
- }
845
-
846
- this._waitForTopologyConnected(topology, options, callback);
847
- }, 500); // this is an arbitrary wait time to allow SDAM to transition
848
- }
849
-
850
827
  /** @internal */
851
- private _closeWithError(error: AnyError, callback?: Callback): void {
852
- if (!callback) {
853
- this.emit(ChangeStream.ERROR, error);
854
- }
828
+ private _closeEmitterModeWithError(error: AnyError): void {
829
+ this.emit(ChangeStream.ERROR, error);
855
830
 
856
- this.close(() => callback && callback(error));
831
+ this.close(() => {
832
+ // nothing to do
833
+ });
857
834
  }
858
835
 
859
836
  /** @internal */
@@ -861,8 +838,15 @@ export class ChangeStream<
861
838
  this._setIsEmitter();
862
839
  const stream = this[kCursorStream] ?? cursor.stream();
863
840
  this[kCursorStream] = stream;
864
- stream.on('data', change => this._processNewChange(change));
865
- stream.on('error', error => this._processError(error));
841
+ stream.on('data', change => {
842
+ try {
843
+ const processedChange = this._processChange(change);
844
+ this.emit(ChangeStream.CHANGE, processedChange);
845
+ } catch (error) {
846
+ this.emit(ChangeStream.ERROR, error);
847
+ }
848
+ });
849
+ stream.on('error', error => this._processErrorStreamMode(error));
866
850
  }
867
851
 
868
852
  /** @internal */
@@ -877,291 +861,81 @@ export class ChangeStream<
877
861
  }
878
862
 
879
863
  /** @internal */
880
- private _processNewChange(change: TChange | null, callback?: Callback<TChange>) {
864
+ private _processChange(change: TChange | null): TChange {
881
865
  if (this[kClosed]) {
882
866
  // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
883
- if (callback) callback(new MongoAPIError(CHANGESTREAM_CLOSED_ERROR));
884
- return;
867
+ throw new MongoAPIError(CHANGESTREAM_CLOSED_ERROR);
885
868
  }
886
869
 
887
870
  // a null change means the cursor has been notified, implicitly closing the change stream
888
871
  if (change == null) {
889
872
  // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
890
- return this._closeWithError(new MongoRuntimeError(CHANGESTREAM_CLOSED_ERROR), callback);
873
+ throw new MongoRuntimeError(CHANGESTREAM_CLOSED_ERROR);
891
874
  }
892
875
 
893
876
  if (change && !change._id) {
894
- return this._closeWithError(new MongoChangeStreamError(NO_RESUME_TOKEN_ERROR), callback);
877
+ throw new MongoChangeStreamError(NO_RESUME_TOKEN_ERROR);
895
878
  }
896
879
 
897
880
  // cache the resume token
898
- this.cursor?.cacheResumeToken(change._id);
881
+ this.cursor.cacheResumeToken(change._id);
899
882
 
900
883
  // wipe the startAtOperationTime if there was one so that there won't be a conflict
901
884
  // between resumeToken and startAtOperationTime if we need to reconnect the cursor
902
885
  this.options.startAtOperationTime = undefined;
903
886
 
904
- // Return the change
905
- if (!callback) return this.emit(ChangeStream.CHANGE, change);
906
- return callback(undefined, change);
887
+ return change;
907
888
  }
908
889
 
909
890
  /** @internal */
910
- private _processError(error: AnyError, callback?: Callback) {
911
- const cursor = this.cursor;
912
-
891
+ private _processErrorStreamMode(changeStreamError: AnyError) {
913
892
  // If the change stream has been closed explicitly, do not process error.
914
- if (this[kClosed]) {
915
- // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
916
- if (callback) callback(new MongoAPIError(CHANGESTREAM_CLOSED_ERROR));
917
- return;
918
- }
893
+ if (this[kClosed]) return;
919
894
 
920
- // if the resume succeeds, continue with the new cursor
921
- const resumeWithCursor = (newCursor: ChangeStreamCursor<TSchema, TChange>) => {
922
- this.cursor = newCursor;
923
- this._processResumeQueue();
924
- };
925
-
926
- // otherwise, raise an error and close the change stream
927
- const unresumableError = (err: AnyError) => {
928
- if (!callback) {
929
- this.emit(ChangeStream.ERROR, err);
930
- }
931
-
932
- this.close(() => this._processResumeQueue(err));
933
- };
934
-
935
- if (cursor && isResumableError(error, maxWireVersion(cursor.server))) {
936
- this.cursor = undefined;
937
-
938
- // stop listening to all events from old cursor
895
+ if (isResumableError(changeStreamError, this.cursor.maxWireVersion)) {
939
896
  this._endStream();
940
-
941
- // close internal cursor, ignore errors
942
- cursor.close();
897
+ this.cursor.close().catch(() => null);
943
898
 
944
899
  const topology = getTopology(this.parent);
945
- this._waitForTopologyConnected(topology, { readPreference: cursor.readPreference }, err => {
946
- // if the topology can't reconnect, close the stream
947
- if (err) return unresumableError(err);
948
-
949
- // create a new cursor, preserving the old cursor's options
950
- const newCursor = this._createChangeStreamCursor(cursor.resumeOptions);
951
-
952
- // attempt to continue in emitter mode
953
- if (!callback) return resumeWithCursor(newCursor);
954
-
955
- // attempt to continue in iterator mode
956
- newCursor.hasNext(err => {
957
- // if there's an error immediately after resuming, close the stream
958
- if (err) return unresumableError(err);
959
- resumeWithCursor(newCursor);
960
- });
900
+ topology.selectServer(this.cursor.readPreference, {}, serverSelectionError => {
901
+ if (serverSelectionError) return this._closeEmitterModeWithError(changeStreamError);
902
+ this.cursor = this._createChangeStreamCursor(this.cursor.resumeOptions);
961
903
  });
962
- return;
963
- }
964
-
965
- // if initial error wasn't resumable, raise an error and close the change stream
966
- return this._closeWithError(error, callback);
967
- }
968
-
969
- /** @internal */
970
- private _getCursor(callback: Callback<ChangeStreamCursor<TSchema, TChange>>) {
971
- if (this[kClosed]) {
972
- // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
973
- callback(new MongoAPIError(CHANGESTREAM_CLOSED_ERROR));
974
- return;
975
- }
976
-
977
- // if a cursor exists and it is open, return it
978
- if (this.cursor) {
979
- callback(undefined, this.cursor);
980
- return;
904
+ } else {
905
+ this._closeEmitterModeWithError(changeStreamError);
981
906
  }
982
-
983
- // no cursor, queue callback until topology reconnects
984
- this[kResumeQueue].push(callback);
985
907
  }
986
908
 
987
909
  /**
988
- * Drain the resume queue when a new has become available
989
910
  * @internal
990
911
  *
991
- * @param error - error getting a new cursor
912
+ * TODO(NODE-4320): promisify selectServer and refactor this code to be async
913
+ *
914
+ * we promisify _processErrorIteratorModeCallback until we have a promisifed version of selectServer.
992
915
  */
993
- private _processResumeQueue(error?: Error) {
994
- while (this[kResumeQueue].length) {
995
- const request = this[kResumeQueue].pop();
996
- if (!request) break; // Should never occur but TS can't use the length check in the while condition
997
-
998
- if (!error) {
999
- if (this[kClosed]) {
1000
- // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
1001
- request(new MongoAPIError(CHANGESTREAM_CLOSED_ERROR));
1002
- return;
1003
- }
1004
- if (!this.cursor) {
1005
- request(new MongoChangeStreamError(NO_CURSOR_ERROR));
1006
- return;
1007
- }
1008
- }
1009
- request(error, this.cursor ?? undefined);
1010
- }
1011
- }
1012
- }
1013
-
1014
- /** @internal */
1015
- export interface ChangeStreamCursorOptions extends AbstractCursorOptions {
1016
- startAtOperationTime?: OperationTime;
1017
- resumeAfter?: ResumeToken;
1018
- startAfter?: ResumeToken;
1019
- maxAwaitTimeMS?: number;
1020
- collation?: CollationOptions;
1021
- fullDocument?: string;
1022
- }
1023
-
1024
- /** @internal */
1025
- export class ChangeStreamCursor<
1026
- TSchema extends Document = Document,
1027
- TChange extends Document = ChangeStreamDocument<TSchema>
1028
- > extends AbstractCursor<TChange, ChangeStreamEvents> {
1029
- _resumeToken: ResumeToken;
1030
- startAtOperationTime?: OperationTime;
1031
- hasReceived?: boolean;
1032
- resumeAfter: ResumeToken;
1033
- startAfter: ResumeToken;
1034
- options: ChangeStreamCursorOptions;
1035
-
1036
- postBatchResumeToken?: ResumeToken;
1037
- pipeline: Document[];
1038
-
1039
- constructor(
1040
- client: MongoClient,
1041
- namespace: MongoDBNamespace,
1042
- pipeline: Document[] = [],
1043
- options: ChangeStreamCursorOptions = {}
1044
- ) {
1045
- super(client, namespace, options);
1046
-
1047
- this.pipeline = pipeline;
1048
- this.options = options;
1049
- this._resumeToken = null;
1050
- this.startAtOperationTime = options.startAtOperationTime;
1051
-
1052
- if (options.startAfter) {
1053
- this.resumeToken = options.startAfter;
1054
- } else if (options.resumeAfter) {
1055
- this.resumeToken = options.resumeAfter;
1056
- }
1057
- }
916
+ // eslint-disable-next-line @typescript-eslint/unbound-method
917
+ private _processErrorIteratorMode = promisify(this._processErrorIteratorModeCallback);
1058
918
 
1059
- set resumeToken(token: ResumeToken) {
1060
- this._resumeToken = token;
1061
- this.emit(ChangeStream.RESUME_TOKEN_CHANGED, token);
1062
- }
1063
-
1064
- get resumeToken(): ResumeToken {
1065
- return this._resumeToken;
1066
- }
1067
-
1068
- get resumeOptions(): ChangeStreamCursorOptions {
1069
- const options: ChangeStreamCursorOptions = {
1070
- ...this.options
1071
- };
1072
-
1073
- for (const key of ['resumeAfter', 'startAfter', 'startAtOperationTime'] as const) {
1074
- delete options[key];
919
+ /** @internal */
920
+ private _processErrorIteratorModeCallback(changeStreamError: AnyError, callback: Callback) {
921
+ if (this[kClosed]) {
922
+ // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
923
+ return callback(new MongoAPIError(CHANGESTREAM_CLOSED_ERROR));
1075
924
  }
1076
925
 
1077
- if (this.resumeToken != null) {
1078
- if (this.options.startAfter && !this.hasReceived) {
1079
- options.startAfter = this.resumeToken;
1080
- } else {
1081
- options.resumeAfter = this.resumeToken;
1082
- }
1083
- } else if (this.startAtOperationTime != null && maxWireVersion(this.server) >= 7) {
1084
- options.startAtOperationTime = this.startAtOperationTime;
1085
- }
926
+ if (isResumableError(changeStreamError, this.cursor.maxWireVersion)) {
927
+ this.cursor.close().catch(() => null);
1086
928
 
1087
- return options;
1088
- }
929
+ const topology = getTopology(this.parent);
930
+ topology.selectServer(this.cursor.readPreference, {}, serverSelectionError => {
931
+ // if the topology can't reconnect, close the stream
932
+ if (serverSelectionError) return this.close(() => callback(changeStreamError));
1089
933
 
1090
- cacheResumeToken(resumeToken: ResumeToken): void {
1091
- if (this.bufferedCount() === 0 && this.postBatchResumeToken) {
1092
- this.resumeToken = this.postBatchResumeToken;
934
+ this.cursor = this._createChangeStreamCursor(this.cursor.resumeOptions);
935
+ callback();
936
+ });
1093
937
  } else {
1094
- this.resumeToken = resumeToken;
1095
- }
1096
- this.hasReceived = true;
1097
- }
1098
-
1099
- _processBatch(response: ChangeStreamAggregateRawResult<TChange>): void {
1100
- const cursor = response.cursor;
1101
- if (cursor.postBatchResumeToken) {
1102
- this.postBatchResumeToken = response.cursor.postBatchResumeToken;
1103
-
1104
- const batch =
1105
- 'firstBatch' in response.cursor ? response.cursor.firstBatch : response.cursor.nextBatch;
1106
- if (batch.length === 0) {
1107
- this.resumeToken = cursor.postBatchResumeToken;
1108
- }
938
+ this.close(() => callback(changeStreamError));
1109
939
  }
1110
940
  }
1111
-
1112
- clone(): AbstractCursor<TChange> {
1113
- return new ChangeStreamCursor(this.client, this.namespace, this.pipeline, {
1114
- ...this.cursorOptions
1115
- });
1116
- }
1117
-
1118
- _initialize(session: ClientSession, callback: Callback<ExecutionResult>): void {
1119
- const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
1120
- ...this.cursorOptions,
1121
- ...this.options,
1122
- session
1123
- });
1124
-
1125
- executeOperation<TODO_NODE_3286, ChangeStreamAggregateRawResult<TChange>>(
1126
- session.client,
1127
- aggregateOperation,
1128
- (err, response) => {
1129
- if (err || response == null) {
1130
- return callback(err);
1131
- }
1132
-
1133
- const server = aggregateOperation.server;
1134
- if (
1135
- this.startAtOperationTime == null &&
1136
- this.resumeAfter == null &&
1137
- this.startAfter == null &&
1138
- maxWireVersion(server) >= 7
1139
- ) {
1140
- this.startAtOperationTime = response.operationTime;
1141
- }
1142
-
1143
- this._processBatch(response);
1144
-
1145
- this.emit(ChangeStream.INIT, response);
1146
- this.emit(ChangeStream.RESPONSE);
1147
-
1148
- // TODO: NODE-2882
1149
- callback(undefined, { server, session, response });
1150
- }
1151
- );
1152
- }
1153
-
1154
- override _getMore(batchSize: number, callback: Callback): void {
1155
- super._getMore(batchSize, (err, response) => {
1156
- if (err) {
1157
- return callback(err);
1158
- }
1159
-
1160
- this._processBatch(response as TODO_NODE_3286 as ChangeStreamAggregateRawResult<TChange>);
1161
-
1162
- this.emit(ChangeStream.MORE, response);
1163
- this.emit(ChangeStream.RESPONSE);
1164
- callback(err, response);
1165
- });
1166
- }
1167
941
  }