mongodb 4.7.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/lib/change_stream.js +136 -271
  2. package/lib/change_stream.js.map +1 -1
  3. package/lib/cmap/command_monitoring_events.js +2 -3
  4. package/lib/cmap/command_monitoring_events.js.map +1 -1
  5. package/lib/cmap/connect.js +3 -6
  6. package/lib/cmap/connect.js.map +1 -1
  7. package/lib/cmap/connection.js.map +1 -1
  8. package/lib/cmap/wire_protocol/compression.js +2 -6
  9. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  10. package/lib/collection.js +18 -3
  11. package/lib/collection.js.map +1 -1
  12. package/lib/cursor/abstract_cursor.js +55 -62
  13. package/lib/cursor/abstract_cursor.js.map +1 -1
  14. package/lib/cursor/change_stream_cursor.js +115 -0
  15. package/lib/cursor/change_stream_cursor.js.map +1 -0
  16. package/lib/cursor/list_collections_cursor.js +37 -0
  17. package/lib/cursor/list_collections_cursor.js.map +1 -0
  18. package/lib/cursor/list_indexes_cursor.js +36 -0
  19. package/lib/cursor/list_indexes_cursor.js.map +1 -0
  20. package/lib/db.js +2 -2
  21. package/lib/db.js.map +1 -1
  22. package/lib/deps.js.map +1 -1
  23. package/lib/encrypter.js +5 -12
  24. package/lib/encrypter.js.map +1 -1
  25. package/lib/index.js +9 -7
  26. package/lib/index.js.map +1 -1
  27. package/lib/mongo_client.js +49 -21
  28. package/lib/mongo_client.js.map +1 -1
  29. package/lib/mongo_types.js.map +1 -1
  30. package/lib/operations/common_functions.js.map +1 -1
  31. package/lib/operations/estimated_document_count.js +5 -0
  32. package/lib/operations/estimated_document_count.js.map +1 -1
  33. package/lib/operations/execute_operation.js +17 -8
  34. package/lib/operations/execute_operation.js.map +1 -1
  35. package/lib/operations/get_more.js +1 -1
  36. package/lib/operations/get_more.js.map +1 -1
  37. package/lib/operations/indexes.js +1 -32
  38. package/lib/operations/indexes.js.map +1 -1
  39. package/lib/operations/kill_cursors.js +22 -0
  40. package/lib/operations/kill_cursors.js.map +1 -0
  41. package/lib/operations/list_collections.js +1 -33
  42. package/lib/operations/list_collections.js.map +1 -1
  43. package/lib/operations/operation.js +4 -1
  44. package/lib/operations/operation.js.map +1 -1
  45. package/lib/read_preference.js.map +1 -1
  46. package/lib/sdam/topology.js +25 -64
  47. package/lib/sdam/topology.js.map +1 -1
  48. package/lib/sessions.js +27 -30
  49. package/lib/sessions.js.map +1 -1
  50. package/lib/utils.js +25 -7
  51. package/lib/utils.js.map +1 -1
  52. package/mongodb.d.ts +60 -27
  53. package/package.json +18 -18
  54. package/src/change_stream.ts +147 -373
  55. package/src/cmap/command_monitoring_events.ts +2 -3
  56. package/src/cmap/connect.ts +20 -25
  57. package/src/cmap/connection.ts +1 -2
  58. package/src/cmap/wire_protocol/compression.ts +8 -6
  59. package/src/collection.ts +21 -7
  60. package/src/cursor/abstract_cursor.ts +66 -71
  61. package/src/cursor/change_stream_cursor.ts +194 -0
  62. package/src/cursor/list_collections_cursor.ts +52 -0
  63. package/src/cursor/list_indexes_cursor.ts +41 -0
  64. package/src/db.ts +2 -5
  65. package/src/deps.ts +13 -22
  66. package/src/encrypter.ts +5 -13
  67. package/src/index.ts +9 -5
  68. package/src/mongo_client.ts +68 -27
  69. package/src/mongo_types.ts +29 -3
  70. package/src/operations/common_functions.ts +1 -1
  71. package/src/operations/estimated_document_count.ts +6 -0
  72. package/src/operations/execute_operation.ts +17 -8
  73. package/src/operations/get_more.ts +2 -2
  74. package/src/operations/indexes.ts +0 -37
  75. package/src/operations/kill_cursors.ts +27 -0
  76. package/src/operations/list_collections.ts +0 -43
  77. package/src/operations/operation.ts +5 -1
  78. package/src/read_preference.ts +5 -9
  79. package/src/sdam/topology.ts +35 -101
  80. package/src/sessions.ts +30 -38
  81. package/src/utils.ts +32 -8
@@ -8,7 +8,6 @@ import type { Document } from '../bson';
8
8
  import { Int32 } from '../bson';
9
9
  import { LEGACY_HELLO_COMMAND } from '../constants';
10
10
  import {
11
- AnyError,
12
11
  MongoCompatibilityError,
13
12
  MongoError,
14
13
  MongoErrorLabel,
@@ -462,40 +461,36 @@ function makeSocks5Connection(options: MakeConnectionOptions, callback: Callback
462
461
  }
463
462
 
464
463
  // Then, establish the Socks5 proxy connection:
465
- SocksClient.createConnection(
466
- {
467
- existing_socket: rawSocket,
468
- timeout: options.connectTimeoutMS,
469
- command: 'connect',
470
- destination: {
471
- host: destination.host,
472
- port: destination.port
473
- },
474
- proxy: {
475
- // host and port are ignored because we pass existing_socket
476
- host: 'iLoveJavaScript',
477
- port: 0,
478
- type: 5,
479
- userId: options.proxyUsername || undefined,
480
- password: options.proxyPassword || undefined
481
- }
464
+ SocksClient.createConnection({
465
+ existing_socket: rawSocket,
466
+ timeout: options.connectTimeoutMS,
467
+ command: 'connect',
468
+ destination: {
469
+ host: destination.host,
470
+ port: destination.port
482
471
  },
483
- (err: AnyError, info: { socket: Stream }) => {
484
- if (err) {
485
- return callback(connectionFailureError('error', err));
486
- }
487
-
472
+ proxy: {
473
+ // host and port are ignored because we pass existing_socket
474
+ host: 'iLoveJavaScript',
475
+ port: 0,
476
+ type: 5,
477
+ userId: options.proxyUsername || undefined,
478
+ password: options.proxyPassword || undefined
479
+ }
480
+ }).then(
481
+ ({ socket }) => {
488
482
  // Finally, now treat the resulting duplex stream as the
489
483
  // socket over which we send and receive wire protocol messages:
490
484
  makeConnection(
491
485
  {
492
486
  ...options,
493
- existingSocket: info.socket,
487
+ existingSocket: socket,
494
488
  proxyHost: undefined
495
489
  },
496
490
  callback
497
491
  );
498
- }
492
+ },
493
+ error => callback(connectionFailureError('error', error))
499
494
  );
500
495
  }
501
496
  );
@@ -533,7 +533,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
533
533
  clusterTime = session.clusterTime;
534
534
  }
535
535
 
536
- const err = applySession(session, finalCmd, options as CommandOptions);
536
+ const err = applySession(session, finalCmd, options);
537
537
  if (err) {
538
538
  return callback(err);
539
539
  }
@@ -727,7 +727,6 @@ export class CryptoConnection extends Connection {
727
727
  callback(err, null);
728
728
  return;
729
729
  }
730
-
731
730
  super.command(ns, encrypted, options, (err, response) => {
732
731
  if (err || response == null) {
733
732
  callback(err, response);
@@ -52,9 +52,10 @@ export function compress(
52
52
  if (Snappy[PKG_VERSION].major <= 6) {
53
53
  Snappy.compress(dataToBeCompressed, callback);
54
54
  } else {
55
- Snappy.compress(dataToBeCompressed)
56
- .then(buffer => callback(undefined, buffer))
57
- .catch(error => callback(error));
55
+ Snappy.compress(dataToBeCompressed).then(
56
+ buffer => callback(undefined, buffer),
57
+ error => callback(error)
58
+ );
58
59
  }
59
60
  break;
60
61
  }
@@ -102,9 +103,10 @@ export function decompress(
102
103
  if (Snappy[PKG_VERSION].major <= 6) {
103
104
  Snappy.uncompress(compressedData, { asBuffer: true }, callback);
104
105
  } else {
105
- Snappy.uncompress(compressedData, { asBuffer: true })
106
- .then(buffer => callback(undefined, buffer))
107
- .catch(error => callback(error));
106
+ Snappy.uncompress(compressedData, { asBuffer: true }).then(
107
+ buffer => callback(undefined, buffer),
108
+ error => callback(error)
109
+ );
108
110
  }
109
111
  break;
110
112
  }
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
  }
@@ -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
  }
@@ -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 */
@@ -118,7 +119,7 @@ export abstract class AbstractCursor<
118
119
  /** @internal */
119
120
  [kId]?: Long;
120
121
  /** @internal */
121
- [kSession]?: ClientSession;
122
+ [kSession]: ClientSession;
122
123
  /** @internal */
123
124
  [kServer]?: Server;
124
125
  /** @internal */
@@ -187,6 +188,8 @@ export abstract class AbstractCursor<
187
188
 
188
189
  if (options.session instanceof ClientSession) {
189
190
  this[kSession] = options.session;
191
+ } else {
192
+ this[kSession] = this[kClient].startSession({ owner: this, explicit: false });
190
193
  }
191
194
  }
192
195
 
@@ -217,11 +220,11 @@ export abstract class AbstractCursor<
217
220
  }
218
221
 
219
222
  /** @internal */
220
- get session(): ClientSession | undefined {
223
+ get session(): ClientSession {
221
224
  return this[kSession];
222
225
  }
223
226
 
224
- set session(clientSession: ClientSession | undefined) {
227
+ set session(clientSession: ClientSession) {
225
228
  this[kSession] = clientSession;
226
229
  }
227
230
 
@@ -264,7 +267,7 @@ export abstract class AbstractCursor<
264
267
  stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> {
265
268
  if (options?.transform) {
266
269
  const transform = options.transform;
267
- const readable = makeCursorStream(this);
270
+ const readable = new ReadableCursorStream(this);
268
271
 
269
272
  return readable.pipe(
270
273
  new Transform({
@@ -282,7 +285,7 @@ export abstract class AbstractCursor<
282
285
  );
283
286
  }
284
287
 
285
- return makeCursorStream(this);
288
+ return new ReadableCursorStream(this);
286
289
  }
287
290
 
288
291
  hasNext(): Promise<boolean>;
@@ -592,11 +595,12 @@ export abstract class AbstractCursor<
592
595
  const session = this[kSession];
593
596
  if (session) {
594
597
  // 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();
598
+ if (session.explicit === false) {
599
+ if (!session.hasEnded) {
600
+ session.endSession().catch(() => null);
601
+ }
602
+ this[kSession] = this.client.startSession({ owner: this, explicit: false });
597
603
  }
598
-
599
- this[kSession] = undefined;
600
604
  }
601
605
  }
602
606
 
@@ -644,22 +648,10 @@ export abstract class AbstractCursor<
644
648
  * a significant refactor.
645
649
  */
646
650
  [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
651
  this._initialize(this[kSession], (err, state) => {
659
652
  if (state) {
660
653
  const response = state.response;
661
654
  this[kServer] = state.server;
662
- this[kSession] = state.session;
663
655
 
664
656
  if (response.cursor) {
665
657
  this[kId] =
@@ -714,7 +706,21 @@ function nextDocument<T>(cursor: AbstractCursor): T | null {
714
706
  return null;
715
707
  }
716
708
 
717
- function next<T>(cursor: AbstractCursor<T>, blocking: boolean, callback: Callback<T | null>): void {
709
+ /**
710
+ * @param cursor - the cursor on which to call `next`
711
+ * @param blocking - a boolean indicating whether or not the cursor should `block` until data
712
+ * is available. Generally, this flag is set to `false` because if the getMore returns no documents,
713
+ * the cursor has been exhausted. In certain scenarios (ChangeStreams, tailable await cursors and
714
+ * `tryNext`, for example) blocking is necessary because a getMore returning no documents does
715
+ * not indicate the end of the cursor.
716
+ * @param callback - callback to return the result to the caller
717
+ * @returns
718
+ */
719
+ export function next<T>(
720
+ cursor: AbstractCursor<T>,
721
+ blocking: boolean,
722
+ callback: Callback<T | null>
723
+ ): void {
718
724
  const cursorId = cursor[kId];
719
725
  if (cursor.closed) {
720
726
  return callback(undefined, null);
@@ -829,11 +835,11 @@ function cleanupCursor(
829
835
  }
830
836
 
831
837
  cursor[kKilled] = true;
832
- server.killCursors(
833
- cursorNs,
834
- [cursorId],
835
- { ...pluckBSONSerializeOptions(cursor[kOptions]), session },
836
- () => completeCleanup()
838
+
839
+ return executeOperation(
840
+ cursor[kClient],
841
+ new KillCursorsOperation(cursorId, cursorNs, server, { session }),
842
+ completeCleanup
837
843
  );
838
844
  }
839
845
 
@@ -844,50 +850,41 @@ export function assertUninitialized(cursor: AbstractCursor): void {
844
850
  }
845
851
  }
846
852
 
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
- }
853
+ class ReadableCursorStream extends Readable {
854
+ private _cursor: AbstractCursor;
855
+ private _readInProgress = false;
863
856
 
864
- if (!reading) {
865
- reading = true;
866
- readNext();
867
- }
868
- };
857
+ constructor(cursor: AbstractCursor) {
858
+ super({
859
+ objectMode: true,
860
+ autoDestroy: false,
861
+ highWaterMark: 1
862
+ });
863
+ this._cursor = cursor;
864
+ }
869
865
 
870
- readable._destroy = function (error, cb) {
871
- if (needToClose) {
872
- cursor.close(err => process.nextTick(cb, err || error));
873
- } else {
874
- cb(error);
866
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
867
+ override _read(size: number): void {
868
+ if (!this._readInProgress) {
869
+ this._readInProgress = true;
870
+ this._readNext();
875
871
  }
876
- };
872
+ }
877
873
 
878
- function readNext() {
879
- needToClose = false;
880
- next(cursor, true, (err, result) => {
881
- needToClose = err ? !cursor.closed : result != null;
874
+ override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {
875
+ this._cursor.close(err => process.nextTick(callback, err || error));
876
+ }
882
877
 
878
+ private _readNext() {
879
+ next(this._cursor, true, (err, result) => {
883
880
  if (err) {
884
881
  // NOTE: This is questionable, but we have a test backing the behavior. It seems the
885
882
  // desired behavior is that a stream ends cleanly when a user explicitly closes
886
883
  // a client during iteration. Alternatively, we could do the "right" thing and
887
884
  // propagate the error message by removing this special case.
888
885
  if (err.message.match(/server is closed/)) {
889
- cursor.close();
890
- return readable.push(null);
886
+ this._cursor.close().catch(() => null);
887
+ return this.push(null);
891
888
  }
892
889
 
893
890
  // NOTE: This is also perhaps questionable. The rationale here is that these errors tend
@@ -896,25 +893,23 @@ function makeCursorStream(cursor: AbstractCursor) {
896
893
  // that changed to happen in cleanup legitimate errors would not destroy the
897
894
  // stream. There are change streams test specifically test these cases.
898
895
  if (err.message.match(/interrupted/)) {
899
- return readable.push(null);
896
+ return this.push(null);
900
897
  }
901
898
 
902
- return readable.destroy(err);
899
+ return this.destroy(err);
903
900
  }
904
901
 
905
902
  if (result == null) {
906
- readable.push(null);
907
- } else if (readable.destroyed) {
908
- cursor.close();
903
+ this.push(null);
904
+ } else if (this.destroyed) {
905
+ this._cursor.close().catch(() => null);
909
906
  } else {
910
- if (readable.push(result)) {
911
- return readNext();
907
+ if (this.push(result)) {
908
+ return this._readNext();
912
909
  }
913
910
 
914
- reading = false;
911
+ this._readInProgress = false;
915
912
  }
916
913
  });
917
914
  }
918
-
919
- return readable;
920
915
  }
@@ -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
+ }