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
package/src/collection.ts CHANGED
@@ -5,6 +5,7 @@ import { UnorderedBulkOperation } from './bulk/unordered';
5
5
  import { ChangeStream, ChangeStreamDocument, ChangeStreamOptions } from './change_stream';
6
6
  import { AggregationCursor } from './cursor/aggregation_cursor';
7
7
  import { FindCursor } from './cursor/find_cursor';
8
+ import { ListIndexesCursor } from './cursor/list_indexes_cursor';
8
9
  import type { Db } from './db';
9
10
  import { MongoInvalidArgumentError } from './error';
10
11
  import type { Logger, LoggerOptions } from './logger';
@@ -57,7 +58,6 @@ import {
57
58
  IndexExistsOperation,
58
59
  IndexInformationOperation,
59
60
  IndexSpecification,
60
- ListIndexesCursor,
61
61
  ListIndexesOptions
62
62
  } from './operations/indexes';
63
63
  import {
@@ -725,7 +725,7 @@ export class Collection<TSchema extends Document = Document> {
725
725
  }
726
726
 
727
727
  if (typeof filter === 'function') {
728
- callback = filter as Callback<WithId<TSchema> | null>;
728
+ callback = filter;
729
729
  filter = {};
730
730
  options = {};
731
731
  }
@@ -1128,7 +1128,7 @@ export class Collection<TSchema extends Document = Document> {
1128
1128
  this.s.db.s.client,
1129
1129
  new CountDocumentsOperation(
1130
1130
  this as TODO_NODE_3286,
1131
- filter as Document,
1131
+ filter,
1132
1132
  resolveOptions(this, options as CountDocumentsOptions)
1133
1133
  ),
1134
1134
  callback
@@ -1191,7 +1191,7 @@ export class Collection<TSchema extends Document = Document> {
1191
1191
  callback?: Callback<any[]>
1192
1192
  ): Promise<any[]> | void {
1193
1193
  if (typeof filter === 'function') {
1194
- (callback = filter as Callback<any[]>), (filter = {}), (options = {});
1194
+ (callback = filter), (filter = {}), (options = {});
1195
1195
  } else {
1196
1196
  if (arguments.length === 3 && typeof options === 'function') {
1197
1197
  (callback = options), (options = {});
@@ -1544,12 +1544,26 @@ export class Collection<TSchema extends Document = Document> {
1544
1544
  );
1545
1545
  }
1546
1546
 
1547
- /** Initiate an Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order. */
1547
+ /**
1548
+ * Initiate an Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order.
1549
+ *
1550
+ * @throws MongoNotConnectedError
1551
+ * @remarks
1552
+ * **NOTE:** MongoClient must be connected prior to calling this method due to a known limitation in this legacy implemenation.
1553
+ * However, `collection.bulkWrite()` provides an equivalent API that does not require prior connecting.
1554
+ */
1548
1555
  initializeUnorderedBulkOp(options?: BulkWriteOptions): UnorderedBulkOperation {
1549
1556
  return new UnorderedBulkOperation(this as TODO_NODE_3286, resolveOptions(this, options));
1550
1557
  }
1551
1558
 
1552
- /** Initiate an In order bulk write operation. Operations will be serially executed in the order they are added, creating a new operation for each switch in types. */
1559
+ /**
1560
+ * Initiate an In order bulk write operation. Operations will be serially executed in the order they are added, creating a new operation for each switch in types.
1561
+ *
1562
+ * @throws MongoNotConnectedError
1563
+ * @remarks
1564
+ * **NOTE:** MongoClient must be connected prior to calling this method due to a known limitation in this legacy implemenation.
1565
+ * However, `collection.bulkWrite()` provides an equivalent API that does not require prior connecting.
1566
+ */
1553
1567
  initializeOrderedBulkOp(options?: BulkWriteOptions): OrderedBulkOperation {
1554
1568
  return new OrderedBulkOperation(this as TODO_NODE_3286, resolveOptions(this, options));
1555
1569
  }
@@ -1596,13 +1610,13 @@ export class Collection<TSchema extends Document = Document> {
1596
1610
  * Updates documents.
1597
1611
  *
1598
1612
  * @deprecated use updateOne, updateMany or bulkWrite
1599
- * @param selector - The selector for the update operation.
1613
+ * @param filter - The filter for the update operation.
1600
1614
  * @param update - The update operations to be applied to the documents
1601
1615
  * @param options - Optional settings for the command
1602
1616
  * @param callback - An optional callback, a Promise will be returned if none is provided
1603
1617
  */
1604
1618
  update(
1605
- selector: Filter<TSchema>,
1619
+ filter: Filter<TSchema>,
1606
1620
  update: UpdateFilter<TSchema>,
1607
1621
  options: UpdateOptions,
1608
1622
  callback: Callback<Document>
@@ -1613,19 +1627,19 @@ export class Collection<TSchema extends Document = Document> {
1613
1627
  if (typeof options === 'function') (callback = options), (options = {});
1614
1628
  options = options ?? {};
1615
1629
 
1616
- return this.updateMany(selector, update, options, callback);
1630
+ return this.updateMany(filter, update, options, callback);
1617
1631
  }
1618
1632
 
1619
1633
  /**
1620
1634
  * Remove documents.
1621
1635
  *
1622
1636
  * @deprecated use deleteOne, deleteMany or bulkWrite
1623
- * @param selector - The selector for the update operation.
1637
+ * @param filter - The filter for the remove operation.
1624
1638
  * @param options - Optional settings for the command
1625
1639
  * @param callback - An optional callback, a Promise will be returned if none is provided
1626
1640
  */
1627
1641
  remove(
1628
- selector: Filter<TSchema>,
1642
+ filter: Filter<TSchema>,
1629
1643
  options: DeleteOptions,
1630
1644
  callback: Callback
1631
1645
  ): Promise<DeleteResult> | void {
@@ -1635,7 +1649,7 @@ export class Collection<TSchema extends Document = Document> {
1635
1649
  if (typeof options === 'function') (callback = options), (options = {});
1636
1650
  options = options ?? {};
1637
1651
 
1638
- return this.deleteMany(selector, options, callback);
1652
+ return this.deleteMany(filter, options, callback);
1639
1653
  }
1640
1654
 
1641
1655
  /**
@@ -1667,7 +1681,7 @@ export class Collection<TSchema extends Document = Document> {
1667
1681
  callback?: Callback<number>
1668
1682
  ): Promise<number> | void {
1669
1683
  if (typeof filter === 'function') {
1670
- (callback = filter as Callback<number>), (filter = {}), (options = {});
1684
+ (callback = filter), (filter = {}), (options = {});
1671
1685
  } else {
1672
1686
  if (typeof options === 'function') (callback = options), (options = {});
1673
1687
  }
@@ -1188,7 +1188,7 @@ export const OPTIONS = {
1188
1188
 
1189
1189
  throw new MongoParseError(`Invalid WriteConcern cannot parse: ${JSON.stringify(value)}`);
1190
1190
  }
1191
- } as OptionDescriptor,
1191
+ },
1192
1192
  wtimeout: {
1193
1193
  deprecated: 'Please use wtimeoutMS instead',
1194
1194
  target: 'writeConcern',
@@ -14,6 +14,7 @@ import type { MongoClient } from '../mongo_client';
14
14
  import { TODO_NODE_3286, TypedEventEmitter } from '../mongo_types';
15
15
  import { executeOperation, ExecutionResult } from '../operations/execute_operation';
16
16
  import { GetMoreOperation } from '../operations/get_more';
17
+ import { KillCursorsOperation } from '../operations/kill_cursors';
17
18
  import { ReadConcern, ReadConcernLike } from '../read_concern';
18
19
  import { ReadPreference, ReadPreferenceLike } from '../read_preference';
19
20
  import type { Server } from '../sdam/server';
@@ -66,7 +67,7 @@ export interface CursorCloseOptions {
66
67
  /** @public */
67
68
  export interface CursorStreamOptions {
68
69
  /** A transformation method applied to each document emitted by the stream */
69
- transform?(doc: Document): Document;
70
+ transform?(this: void, doc: Document): Document;
70
71
  }
71
72
 
72
73
  /** @public */
@@ -77,8 +78,20 @@ export interface AbstractCursorOptions extends BSONSerializeOptions {
77
78
  session?: ClientSession;
78
79
  readPreference?: ReadPreferenceLike;
79
80
  readConcern?: ReadConcernLike;
81
+ /**
82
+ * Specifies the number of documents to return in each response from MongoDB
83
+ */
80
84
  batchSize?: number;
85
+ /**
86
+ * When applicable `maxTimeMS` controls the amount of time the initial command
87
+ * that constructs a cursor should take. (ex. find, aggregate, listCollections)
88
+ */
81
89
  maxTimeMS?: number;
90
+ /**
91
+ * When applicable `maxAwaitTimeMS` controls the amount of time subsequent getMores
92
+ * that a cursor uses to fetch more data should take. (ex. cursor.next())
93
+ */
94
+ maxAwaitTimeMS?: number;
82
95
  /**
83
96
  * Comment to apply to the operation.
84
97
  *
@@ -88,7 +101,19 @@ export interface AbstractCursorOptions extends BSONSerializeOptions {
88
101
  * In server versions 4.4 and above, 'comment' can be any valid BSON type.
89
102
  */
90
103
  comment?: unknown;
104
+ /**
105
+ * By default, MongoDB will automatically close a cursor when the
106
+ * client has exhausted all results in the cursor. However, for [capped collections](https://www.mongodb.com/docs/manual/core/capped-collections)
107
+ * you may use a Tailable Cursor that remains open after the client exhausts
108
+ * the results in the initial cursor.
109
+ */
91
110
  tailable?: boolean;
111
+ /**
112
+ * If awaitData is set to true, when the cursor reaches the end of the capped collection,
113
+ * MongoDB blocks the query thread for a period of time waiting for new data to arrive.
114
+ * When new data is inserted into the capped collection, the blocked thread is signaled
115
+ * to wake up and return the next batch to the client.
116
+ */
92
117
  awaitData?: boolean;
93
118
  noCursorTimeout?: boolean;
94
119
  }
@@ -118,7 +143,7 @@ export abstract class AbstractCursor<
118
143
  /** @internal */
119
144
  [kId]?: Long;
120
145
  /** @internal */
121
- [kSession]?: ClientSession;
146
+ [kSession]: ClientSession;
122
147
  /** @internal */
123
148
  [kServer]?: Server;
124
149
  /** @internal */
@@ -154,7 +179,7 @@ export abstract class AbstractCursor<
154
179
  }
155
180
  this[kClient] = client;
156
181
  this[kNamespace] = namespace;
157
- this[kDocuments] = []; // TODO: https://github.com/microsoft/TypeScript/issues/36230
182
+ this[kDocuments] = [];
158
183
  this[kInitialized] = false;
159
184
  this[kClosed] = false;
160
185
  this[kKilled] = false;
@@ -185,8 +210,14 @@ export abstract class AbstractCursor<
185
210
  this[kOptions].maxTimeMS = options.maxTimeMS;
186
211
  }
187
212
 
213
+ if (typeof options.maxAwaitTimeMS === 'number') {
214
+ this[kOptions].maxAwaitTimeMS = options.maxAwaitTimeMS;
215
+ }
216
+
188
217
  if (options.session instanceof ClientSession) {
189
218
  this[kSession] = options.session;
219
+ } else {
220
+ this[kSession] = this[kClient].startSession({ owner: this, explicit: false });
190
221
  }
191
222
  }
192
223
 
@@ -217,11 +248,11 @@ export abstract class AbstractCursor<
217
248
  }
218
249
 
219
250
  /** @internal */
220
- get session(): ClientSession | undefined {
251
+ get session(): ClientSession {
221
252
  return this[kSession];
222
253
  }
223
254
 
224
- set session(clientSession: ClientSession | undefined) {
255
+ set session(clientSession: ClientSession) {
225
256
  this[kSession] = clientSession;
226
257
  }
227
258
 
@@ -264,7 +295,7 @@ export abstract class AbstractCursor<
264
295
  stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> {
265
296
  if (options?.transform) {
266
297
  const transform = options.transform;
267
- const readable = makeCursorStream(this);
298
+ const readable = new ReadableCursorStream(this);
268
299
 
269
300
  return readable.pipe(
270
301
  new Transform({
@@ -282,7 +313,7 @@ export abstract class AbstractCursor<
282
313
  );
283
314
  }
284
315
 
285
- return makeCursorStream(this);
316
+ return new ReadableCursorStream(this);
286
317
  }
287
318
 
288
319
  hasNext(): Promise<boolean>;
@@ -592,11 +623,12 @@ export abstract class AbstractCursor<
592
623
  const session = this[kSession];
593
624
  if (session) {
594
625
  // We only want to end this session if we created it, and it hasn't ended yet
595
- if (session.explicit === false && !session.hasEnded) {
596
- session.endSession();
626
+ if (session.explicit === false) {
627
+ if (!session.hasEnded) {
628
+ session.endSession().catch(() => null);
629
+ }
630
+ this[kSession] = this.client.startSession({ owner: this, explicit: false });
597
631
  }
598
-
599
- this[kSession] = undefined;
600
632
  }
601
633
  }
602
634
 
@@ -613,21 +645,8 @@ export abstract class AbstractCursor<
613
645
 
614
646
  /** @internal */
615
647
  _getMore(batchSize: number, callback: Callback<Document>): void {
616
- const cursorId = this[kId];
617
- const cursorNs = this[kNamespace];
618
- const server = this[kServer];
619
-
620
- if (cursorId == null) {
621
- callback(new MongoRuntimeError('Unable to iterate cursor with no id'));
622
- return;
623
- }
624
-
625
- if (server == null) {
626
- callback(new MongoRuntimeError('Unable to iterate cursor without selected server'));
627
- return;
628
- }
629
-
630
- const getMoreOperation = new GetMoreOperation(cursorNs, cursorId, server, {
648
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
649
+ const getMoreOperation = new GetMoreOperation(this[kNamespace], this[kId]!, this[kServer]!, {
631
650
  ...this[kOptions],
632
651
  session: this[kSession],
633
652
  batchSize
@@ -644,24 +663,13 @@ export abstract class AbstractCursor<
644
663
  * a significant refactor.
645
664
  */
646
665
  [kInit](callback: Callback<TSchema | null>): void {
647
- if (this[kSession] == null) {
648
- if (this[kClient].topology?.shouldCheckForSessionSupport()) {
649
- return this[kClient].topology?.selectServer(ReadPreference.primaryPreferred, {}, err => {
650
- if (err) return callback(err);
651
- return this[kInit](callback);
652
- });
653
- } else if (this[kClient].topology?.hasSessionSupport()) {
654
- this[kSession] = this[kClient].topology?.startSession({ owner: this, explicit: false });
655
- }
656
- }
657
-
658
666
  this._initialize(this[kSession], (err, state) => {
659
667
  if (state) {
660
668
  const response = state.response;
661
669
  this[kServer] = state.server;
662
- this[kSession] = state.session;
663
670
 
664
671
  if (response.cursor) {
672
+ // TODO(NODE-2674): Preserve int64 sent from MongoDB
665
673
  this[kId] =
666
674
  typeof response.cursor.id === 'number'
667
675
  ? Long.fromNumber(response.cursor.id)
@@ -714,7 +722,21 @@ function nextDocument<T>(cursor: AbstractCursor): T | null {
714
722
  return null;
715
723
  }
716
724
 
717
- function next<T>(cursor: AbstractCursor<T>, blocking: boolean, callback: Callback<T | null>): void {
725
+ /**
726
+ * @param cursor - the cursor on which to call `next`
727
+ * @param blocking - a boolean indicating whether or not the cursor should `block` until data
728
+ * is available. Generally, this flag is set to `false` because if the getMore returns no documents,
729
+ * the cursor has been exhausted. In certain scenarios (ChangeStreams, tailable await cursors and
730
+ * `tryNext`, for example) blocking is necessary because a getMore returning no documents does
731
+ * not indicate the end of the cursor.
732
+ * @param callback - callback to return the result to the caller
733
+ * @returns
734
+ */
735
+ export function next<T>(
736
+ cursor: AbstractCursor<T>,
737
+ blocking: boolean,
738
+ callback: Callback<T | null>
739
+ ): void {
718
740
  const cursorId = cursor[kId];
719
741
  if (cursor.closed) {
720
742
  return callback(undefined, null);
@@ -829,11 +851,11 @@ function cleanupCursor(
829
851
  }
830
852
 
831
853
  cursor[kKilled] = true;
832
- server.killCursors(
833
- cursorNs,
834
- [cursorId],
835
- { ...pluckBSONSerializeOptions(cursor[kOptions]), session },
836
- () => completeCleanup()
854
+
855
+ return executeOperation(
856
+ cursor[kClient],
857
+ new KillCursorsOperation(cursorId, cursorNs, server, { session }),
858
+ completeCleanup
837
859
  );
838
860
  }
839
861
 
@@ -844,50 +866,41 @@ export function assertUninitialized(cursor: AbstractCursor): void {
844
866
  }
845
867
  }
846
868
 
847
- function makeCursorStream(cursor: AbstractCursor) {
848
- const readable = new Readable({
849
- objectMode: true,
850
- autoDestroy: false,
851
- highWaterMark: 1
852
- });
853
-
854
- let initialized = false;
855
- let reading = false;
856
- let needToClose = true; // NOTE: we must close the cursor if we never read from it, use `_construct` in future node versions
857
-
858
- readable._read = function () {
859
- if (initialized === false) {
860
- needToClose = false;
861
- initialized = true;
862
- }
869
+ class ReadableCursorStream extends Readable {
870
+ private _cursor: AbstractCursor;
871
+ private _readInProgress = false;
863
872
 
864
- if (!reading) {
865
- reading = true;
866
- readNext();
867
- }
868
- };
873
+ constructor(cursor: AbstractCursor) {
874
+ super({
875
+ objectMode: true,
876
+ autoDestroy: false,
877
+ highWaterMark: 1
878
+ });
879
+ this._cursor = cursor;
880
+ }
869
881
 
870
- readable._destroy = function (error, cb) {
871
- if (needToClose) {
872
- cursor.close(err => process.nextTick(cb, err || error));
873
- } else {
874
- cb(error);
882
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
883
+ override _read(size: number): void {
884
+ if (!this._readInProgress) {
885
+ this._readInProgress = true;
886
+ this._readNext();
875
887
  }
876
- };
888
+ }
877
889
 
878
- function readNext() {
879
- needToClose = false;
880
- next(cursor, true, (err, result) => {
881
- needToClose = err ? !cursor.closed : result != null;
890
+ override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {
891
+ this._cursor.close(err => process.nextTick(callback, err || error));
892
+ }
882
893
 
894
+ private _readNext() {
895
+ next(this._cursor, true, (err, result) => {
883
896
  if (err) {
884
897
  // NOTE: This is questionable, but we have a test backing the behavior. It seems the
885
898
  // desired behavior is that a stream ends cleanly when a user explicitly closes
886
899
  // a client during iteration. Alternatively, we could do the "right" thing and
887
900
  // propagate the error message by removing this special case.
888
901
  if (err.message.match(/server is closed/)) {
889
- cursor.close();
890
- return readable.push(null);
902
+ this._cursor.close().catch(() => null);
903
+ return this.push(null);
891
904
  }
892
905
 
893
906
  // NOTE: This is also perhaps questionable. The rationale here is that these errors tend
@@ -896,25 +909,23 @@ function makeCursorStream(cursor: AbstractCursor) {
896
909
  // that changed to happen in cleanup legitimate errors would not destroy the
897
910
  // stream. There are change streams test specifically test these cases.
898
911
  if (err.message.match(/interrupted/)) {
899
- return readable.push(null);
912
+ return this.push(null);
900
913
  }
901
914
 
902
- return readable.destroy(err);
915
+ return this.destroy(err);
903
916
  }
904
917
 
905
918
  if (result == null) {
906
- readable.push(null);
907
- } else if (readable.destroyed) {
908
- cursor.close();
919
+ this.push(null);
920
+ } else if (this.destroyed) {
921
+ this._cursor.close().catch(() => null);
909
922
  } else {
910
- if (readable.push(result)) {
911
- return readNext();
923
+ if (this.push(result)) {
924
+ return this._readNext();
912
925
  }
913
926
 
914
- reading = false;
927
+ this._readInProgress = false;
915
928
  }
916
929
  });
917
930
  }
918
-
919
- return readable;
920
931
  }
@@ -0,0 +1,194 @@
1
+ import type { Document, Long, Timestamp } from '../bson';
2
+ import {
3
+ type ChangeStreamDocument,
4
+ type ChangeStreamEvents,
5
+ type OperationTime,
6
+ type ResumeToken,
7
+ ChangeStream
8
+ } from '../change_stream';
9
+ import { INIT, RESPONSE } from '../constants';
10
+ import type { MongoClient } from '../mongo_client';
11
+ import type { TODO_NODE_3286 } from '../mongo_types';
12
+ import { AggregateOperation } from '../operations/aggregate';
13
+ import type { CollationOptions } from '../operations/command';
14
+ import { type ExecutionResult, executeOperation } from '../operations/execute_operation';
15
+ import type { ClientSession } from '../sessions';
16
+ import { type Callback, type MongoDBNamespace, maxWireVersion } from '../utils';
17
+ import { type AbstractCursorOptions, AbstractCursor } from './abstract_cursor';
18
+
19
+ /** @internal */
20
+ export interface ChangeStreamCursorOptions extends AbstractCursorOptions {
21
+ startAtOperationTime?: OperationTime;
22
+ resumeAfter?: ResumeToken;
23
+ startAfter?: ResumeToken;
24
+ maxAwaitTimeMS?: number;
25
+ collation?: CollationOptions;
26
+ fullDocument?: string;
27
+ }
28
+
29
+ /** @internal */
30
+ export type ChangeStreamAggregateRawResult<TChange> = {
31
+ $clusterTime: { clusterTime: Timestamp };
32
+ cursor: {
33
+ postBatchResumeToken: ResumeToken;
34
+ ns: string;
35
+ id: number | Long;
36
+ } & ({ firstBatch: TChange[] } | { nextBatch: TChange[] });
37
+ ok: 1;
38
+ operationTime: Timestamp;
39
+ };
40
+
41
+ /** @internal */
42
+ export class ChangeStreamCursor<
43
+ TSchema extends Document = Document,
44
+ TChange extends Document = ChangeStreamDocument<TSchema>
45
+ > extends AbstractCursor<TChange, ChangeStreamEvents> {
46
+ _resumeToken: ResumeToken;
47
+ startAtOperationTime?: OperationTime;
48
+ hasReceived?: boolean;
49
+ resumeAfter: ResumeToken;
50
+ startAfter: ResumeToken;
51
+ options: ChangeStreamCursorOptions;
52
+
53
+ postBatchResumeToken?: ResumeToken;
54
+ pipeline: Document[];
55
+
56
+ /**
57
+ * @internal
58
+ *
59
+ * used to determine change stream resumability
60
+ */
61
+ maxWireVersion: number | undefined;
62
+
63
+ constructor(
64
+ client: MongoClient,
65
+ namespace: MongoDBNamespace,
66
+ pipeline: Document[] = [],
67
+ options: ChangeStreamCursorOptions = {}
68
+ ) {
69
+ super(client, namespace, options);
70
+
71
+ this.pipeline = pipeline;
72
+ this.options = options;
73
+ this._resumeToken = null;
74
+ this.startAtOperationTime = options.startAtOperationTime;
75
+
76
+ if (options.startAfter) {
77
+ this.resumeToken = options.startAfter;
78
+ } else if (options.resumeAfter) {
79
+ this.resumeToken = options.resumeAfter;
80
+ }
81
+ }
82
+
83
+ set resumeToken(token: ResumeToken) {
84
+ this._resumeToken = token;
85
+ this.emit(ChangeStream.RESUME_TOKEN_CHANGED, token);
86
+ }
87
+
88
+ get resumeToken(): ResumeToken {
89
+ return this._resumeToken;
90
+ }
91
+
92
+ get resumeOptions(): ChangeStreamCursorOptions {
93
+ const options: ChangeStreamCursorOptions = {
94
+ ...this.options
95
+ };
96
+
97
+ for (const key of ['resumeAfter', 'startAfter', 'startAtOperationTime'] as const) {
98
+ delete options[key];
99
+ }
100
+
101
+ if (this.resumeToken != null) {
102
+ if (this.options.startAfter && !this.hasReceived) {
103
+ options.startAfter = this.resumeToken;
104
+ } else {
105
+ options.resumeAfter = this.resumeToken;
106
+ }
107
+ } else if (this.startAtOperationTime != null && maxWireVersion(this.server) >= 7) {
108
+ options.startAtOperationTime = this.startAtOperationTime;
109
+ }
110
+
111
+ return options;
112
+ }
113
+
114
+ cacheResumeToken(resumeToken: ResumeToken): void {
115
+ if (this.bufferedCount() === 0 && this.postBatchResumeToken) {
116
+ this.resumeToken = this.postBatchResumeToken;
117
+ } else {
118
+ this.resumeToken = resumeToken;
119
+ }
120
+ this.hasReceived = true;
121
+ }
122
+
123
+ _processBatch(response: ChangeStreamAggregateRawResult<TChange>): void {
124
+ const cursor = response.cursor;
125
+ if (cursor.postBatchResumeToken) {
126
+ this.postBatchResumeToken = response.cursor.postBatchResumeToken;
127
+
128
+ const batch =
129
+ 'firstBatch' in response.cursor ? response.cursor.firstBatch : response.cursor.nextBatch;
130
+ if (batch.length === 0) {
131
+ this.resumeToken = cursor.postBatchResumeToken;
132
+ }
133
+ }
134
+ }
135
+
136
+ clone(): AbstractCursor<TChange> {
137
+ return new ChangeStreamCursor(this.client, this.namespace, this.pipeline, {
138
+ ...this.cursorOptions
139
+ });
140
+ }
141
+
142
+ _initialize(session: ClientSession, callback: Callback<ExecutionResult>): void {
143
+ const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
144
+ ...this.cursorOptions,
145
+ ...this.options,
146
+ session
147
+ });
148
+
149
+ executeOperation<TODO_NODE_3286, ChangeStreamAggregateRawResult<TChange>>(
150
+ session.client,
151
+ aggregateOperation,
152
+ (err, response) => {
153
+ if (err || response == null) {
154
+ return callback(err);
155
+ }
156
+
157
+ const server = aggregateOperation.server;
158
+ this.maxWireVersion = maxWireVersion(server);
159
+
160
+ if (
161
+ this.startAtOperationTime == null &&
162
+ this.resumeAfter == null &&
163
+ this.startAfter == null &&
164
+ this.maxWireVersion >= 7
165
+ ) {
166
+ this.startAtOperationTime = response.operationTime;
167
+ }
168
+
169
+ this._processBatch(response);
170
+
171
+ this.emit(INIT, response);
172
+ this.emit(RESPONSE);
173
+
174
+ // TODO: NODE-2882
175
+ callback(undefined, { server, session, response });
176
+ }
177
+ );
178
+ }
179
+
180
+ override _getMore(batchSize: number, callback: Callback): void {
181
+ super._getMore(batchSize, (err, response) => {
182
+ if (err) {
183
+ return callback(err);
184
+ }
185
+
186
+ this.maxWireVersion = maxWireVersion(this.server);
187
+ this._processBatch(response as TODO_NODE_3286 as ChangeStreamAggregateRawResult<TChange>);
188
+
189
+ this.emit(ChangeStream.MORE, response);
190
+ this.emit(ChangeStream.RESPONSE);
191
+ callback(err, response);
192
+ });
193
+ }
194
+ }