mongodb 3.2.5 → 3.3.0-beta2

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 (133) hide show
  1. package/HISTORY.md +0 -10
  2. package/index.js +4 -4
  3. package/lib/admin.js +56 -56
  4. package/lib/aggregation_cursor.js +7 -3
  5. package/lib/bulk/common.js +18 -13
  6. package/lib/change_stream.js +196 -89
  7. package/lib/collection.js +217 -169
  8. package/lib/command_cursor.js +17 -7
  9. package/lib/core/auth/auth_provider.js +158 -0
  10. package/lib/core/auth/defaultAuthProviders.js +29 -0
  11. package/lib/core/auth/gssapi.js +241 -0
  12. package/lib/core/auth/mongo_credentials.js +81 -0
  13. package/lib/core/auth/mongocr.js +51 -0
  14. package/lib/core/auth/plain.js +35 -0
  15. package/lib/core/auth/scram.js +293 -0
  16. package/lib/core/auth/sspi.js +131 -0
  17. package/lib/core/auth/x509.js +26 -0
  18. package/lib/core/connection/apm.js +236 -0
  19. package/lib/core/connection/command_result.js +36 -0
  20. package/lib/core/connection/commands.js +507 -0
  21. package/lib/core/connection/connect.js +370 -0
  22. package/lib/core/connection/connection.js +624 -0
  23. package/lib/core/connection/logger.js +246 -0
  24. package/lib/core/connection/msg.js +219 -0
  25. package/lib/core/connection/pool.js +1285 -0
  26. package/lib/core/connection/utils.js +57 -0
  27. package/lib/core/cursor.js +752 -0
  28. package/lib/core/error.js +186 -0
  29. package/lib/core/index.js +50 -0
  30. package/lib/core/sdam/monitoring.js +228 -0
  31. package/lib/core/sdam/server.js +467 -0
  32. package/lib/core/sdam/server_description.js +163 -0
  33. package/lib/core/sdam/server_selectors.js +244 -0
  34. package/lib/core/sdam/srv_polling.js +135 -0
  35. package/lib/core/sdam/topology.js +1151 -0
  36. package/lib/core/sdam/topology_description.js +408 -0
  37. package/lib/core/sessions.js +711 -0
  38. package/lib/core/tools/smoke_plugin.js +61 -0
  39. package/lib/core/topologies/mongos.js +1337 -0
  40. package/lib/core/topologies/read_preference.js +202 -0
  41. package/lib/core/topologies/replset.js +1507 -0
  42. package/lib/core/topologies/replset_state.js +1121 -0
  43. package/lib/core/topologies/server.js +984 -0
  44. package/lib/core/topologies/shared.js +453 -0
  45. package/lib/core/transactions.js +167 -0
  46. package/lib/core/uri_parser.js +631 -0
  47. package/lib/core/utils.js +165 -0
  48. package/lib/core/wireprotocol/command.js +170 -0
  49. package/lib/core/wireprotocol/compression.js +73 -0
  50. package/lib/core/wireprotocol/constants.js +13 -0
  51. package/lib/core/wireprotocol/get_more.js +86 -0
  52. package/lib/core/wireprotocol/index.js +18 -0
  53. package/lib/core/wireprotocol/kill_cursors.js +70 -0
  54. package/lib/core/wireprotocol/query.js +224 -0
  55. package/lib/core/wireprotocol/shared.js +115 -0
  56. package/lib/core/wireprotocol/write_command.js +50 -0
  57. package/lib/cursor.js +40 -46
  58. package/lib/db.js +141 -95
  59. package/lib/dynamic_loaders.js +32 -0
  60. package/lib/error.js +12 -10
  61. package/lib/gridfs/chunk.js +2 -2
  62. package/lib/gridfs/grid_store.js +31 -25
  63. package/lib/gridfs-stream/index.js +4 -4
  64. package/lib/gridfs-stream/upload.js +1 -1
  65. package/lib/mongo_client.js +37 -15
  66. package/lib/operations/add_user.js +96 -0
  67. package/lib/operations/aggregate.js +24 -13
  68. package/lib/operations/aggregate_operation.js +127 -0
  69. package/lib/operations/bulk_write.js +104 -0
  70. package/lib/operations/close.js +47 -0
  71. package/lib/operations/collection_ops.js +28 -287
  72. package/lib/operations/collections.js +55 -0
  73. package/lib/operations/command.js +120 -0
  74. package/lib/operations/command_v2.js +43 -0
  75. package/lib/operations/common_functions.js +372 -0
  76. package/lib/operations/{mongo_client_ops.js → connect.js} +185 -157
  77. package/lib/operations/count.js +72 -0
  78. package/lib/operations/count_documents.js +46 -0
  79. package/lib/operations/create_collection.js +118 -0
  80. package/lib/operations/create_index.js +92 -0
  81. package/lib/operations/create_indexes.js +61 -0
  82. package/lib/operations/cursor_ops.js +3 -4
  83. package/lib/operations/db_ops.js +15 -12
  84. package/lib/operations/delete_many.js +25 -0
  85. package/lib/operations/delete_one.js +25 -0
  86. package/lib/operations/distinct.js +85 -0
  87. package/lib/operations/drop.js +53 -0
  88. package/lib/operations/drop_index.js +42 -0
  89. package/lib/operations/drop_indexes.js +23 -0
  90. package/lib/operations/estimated_document_count.js +33 -0
  91. package/lib/operations/execute_db_admin_command.js +34 -0
  92. package/lib/operations/execute_operation.js +165 -0
  93. package/lib/operations/explain.js +23 -0
  94. package/lib/operations/find_and_modify.js +98 -0
  95. package/lib/operations/find_one.js +33 -0
  96. package/lib/operations/find_one_and_delete.js +16 -0
  97. package/lib/operations/find_one_and_replace.js +18 -0
  98. package/lib/operations/find_one_and_update.js +19 -0
  99. package/lib/operations/geo_haystack_search.js +79 -0
  100. package/lib/operations/has_next.js +40 -0
  101. package/lib/operations/index_exists.js +39 -0
  102. package/lib/operations/index_information.js +23 -0
  103. package/lib/operations/indexes.js +22 -0
  104. package/lib/operations/insert_many.js +63 -0
  105. package/lib/operations/insert_one.js +75 -0
  106. package/lib/operations/is_capped.js +19 -0
  107. package/lib/operations/list_indexes.js +66 -0
  108. package/lib/operations/map_reduce.js +189 -0
  109. package/lib/operations/next.js +32 -0
  110. package/lib/operations/operation.js +63 -0
  111. package/lib/operations/options_operation.js +32 -0
  112. package/lib/operations/profiling_level.js +31 -0
  113. package/lib/operations/re_index.js +28 -0
  114. package/lib/operations/remove_user.js +52 -0
  115. package/lib/operations/rename.js +61 -0
  116. package/lib/operations/replace_one.js +47 -0
  117. package/lib/operations/set_profiling_level.js +48 -0
  118. package/lib/operations/stats.js +45 -0
  119. package/lib/operations/to_array.js +68 -0
  120. package/lib/operations/update_many.js +29 -0
  121. package/lib/operations/update_one.js +44 -0
  122. package/lib/operations/validate_collection.js +40 -0
  123. package/lib/read_concern.js +55 -0
  124. package/lib/topologies/mongos.js +3 -3
  125. package/lib/topologies/native_topology.js +22 -2
  126. package/lib/topologies/replset.js +3 -3
  127. package/lib/topologies/server.js +4 -4
  128. package/lib/topologies/topology_base.js +6 -6
  129. package/lib/url_parser.js +4 -3
  130. package/lib/utils.js +46 -59
  131. package/lib/write_concern.js +66 -0
  132. package/package.json +15 -6
  133. package/lib/.DS_Store +0 -0
@@ -2,9 +2,17 @@
2
2
 
3
3
  const EventEmitter = require('events');
4
4
  const isResumableError = require('./error').isResumableError;
5
- const MongoError = require('mongodb-core').MongoError;
6
-
7
- var cursorOptionNames = ['maxAwaitTimeMS', 'collation', 'readPreference'];
5
+ const MongoError = require('./core').MongoError;
6
+ const ReadConcern = require('./read_concern');
7
+ const MongoDBNamespace = require('./utils').MongoDBNamespace;
8
+ const Cursor = require('./cursor');
9
+ const relayEvents = require('./core/utils').relayEvents;
10
+ const maxWireVersion = require('./core/utils').maxWireVersion;
11
+
12
+ const CHANGE_STREAM_OPTIONS = ['resumeAfter', 'startAfter', 'startAtOperationTime', 'fullDocument'];
13
+ const CURSOR_OPTIONS = ['batchSize', 'maxAwaitTimeMS', 'collation', 'readPreference'].concat(
14
+ CHANGE_STREAM_OPTIONS
15
+ );
8
16
 
9
17
  const CHANGE_DOMAIN_TYPES = {
10
18
  COLLECTION: Symbol('Collection'),
@@ -12,26 +20,45 @@ const CHANGE_DOMAIN_TYPES = {
12
20
  CLUSTER: Symbol('Cluster')
13
21
  };
14
22
 
23
+ /**
24
+ * @typedef ResumeToken
25
+ * @description Represents the logical starting point for a new or resuming {@link ChangeStream} on the server.
26
+ * @see https://docs.mongodb.com/master/changeStreams/#change-stream-resume-token
27
+ */
28
+
29
+ /**
30
+ * @typedef OperationTime
31
+ * @description Represents a specific point in time on a server. Can be retrieved by using {@link Db#command}
32
+ * @see https://docs.mongodb.com/manual/reference/method/db.runCommand/#response
33
+ */
34
+
35
+ /**
36
+ * @typedef ChangeStreamOptions
37
+ * @description 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.
38
+ * @property {string} [fullDocument='default'] Allowed values: ‘default’, ‘updateLookup’. When set to ‘updateLookup’, the change stream will include both a delta describing the changes to the document, as well as a copy of the entire document that was changed from some time after the change occurred.
39
+ * @property {number} [maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a change stream query.
40
+ * @property {ResumeToken} [resumeAfter] Allows you to start a changeStream after a specified event. See {@link https://docs.mongodb.com/master/changeStreams/#resumeafter-for-change-streams|ChangeStream documentation}.
41
+ * @property {ResumeToken} [startAfter] Similar to resumeAfter, but will allow you to start after an invalidated event. See {@link https://docs.mongodb.com/master/changeStreams/#startafter-for-change-streams|ChangeStream documentation}.
42
+ * @property {OperationTime} [startAtOperationTime] Will start the changeStream after the specified operationTime.
43
+ * @property {number} [batchSize] The number of documents to return per batch. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
44
+ * @property {object} [collation] Specify collation settings for operation. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
45
+ * @property {ReadPreference} [readPreference] The read preference. Defaults to the read preference of the database or collection. See {@link https://docs.mongodb.com/manual/reference/read-preference|read preference documentation}.
46
+ */
47
+
15
48
  /**
16
49
  * Creates a new Change Stream instance. Normally created using {@link Collection#watch|Collection.watch()}.
17
50
  * @class ChangeStream
18
51
  * @since 3.0.0
19
52
  * @param {(MongoClient|Db|Collection)} changeDomain The domain against which to create the change stream
20
53
  * @param {Array} pipeline An array of {@link https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents
21
- * @param {object} [options] Optional settings
22
- * @param {string} [options.fullDocument='default'] Allowed values: ‘default’, ‘updateLookup’. When set to ‘updateLookup’, the change stream will include both a delta describing the changes to the document, as well as a copy of the entire document that was changed from some time after the change occurred.
23
- * @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a change stream query
24
- * @param {object} [options.resumeAfter] Specifies the logical starting point for the new change stream. This should be the _id field from a previously returned change stream document.
25
- * @param {number} [options.batchSize] The number of documents to return per batch. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
26
- * @param {object} [options.collation] Specify collation settings for operation. See {@link https://docs.mongodb.com/manual/reference/command/aggregate|aggregation documentation}.
27
- * @param {ReadPreference} [options.readPreference] The read preference. Defaults to the read preference of the database or collection. See {@link https://docs.mongodb.com/manual/reference/read-preference|read preference documentation}.
54
+ * @param {ChangeStreamOptions} [options] Optional settings
28
55
  * @fires ChangeStream#close
29
56
  * @fires ChangeStream#change
30
57
  * @fires ChangeStream#end
31
58
  * @fires ChangeStream#error
59
+ * @fires ChangeStream#resumeTokenChanged
32
60
  * @return {ChangeStream} a ChangeStream instance.
33
61
  */
34
-
35
62
  class ChangeStream extends EventEmitter {
36
63
  constructor(changeDomain, pipeline, options) {
37
64
  super();
@@ -41,28 +68,20 @@ class ChangeStream extends EventEmitter {
41
68
 
42
69
  this.pipeline = pipeline || [];
43
70
  this.options = options || {};
44
- this.cursorNamespace = undefined;
45
- this.namespace = {};
71
+
72
+ this.namespace =
73
+ changeDomain instanceof MongoClient
74
+ ? new MongoDBNamespace('admin')
75
+ : changeDomain.s.namespace;
46
76
 
47
77
  if (changeDomain instanceof Collection) {
48
78
  this.type = CHANGE_DOMAIN_TYPES.COLLECTION;
49
79
  this.topology = changeDomain.s.db.serverConfig;
50
-
51
- this.namespace = {
52
- collection: changeDomain.collectionName,
53
- database: changeDomain.s.db.databaseName
54
- };
55
-
56
- this.cursorNamespace = `${this.namespace.database}.${this.namespace.collection}`;
57
80
  } else if (changeDomain instanceof Db) {
58
81
  this.type = CHANGE_DOMAIN_TYPES.DATABASE;
59
- this.namespace = { collection: '', database: changeDomain.databaseName };
60
- this.cursorNamespace = this.namespace.database;
61
82
  this.topology = changeDomain.serverConfig;
62
83
  } else if (changeDomain instanceof MongoClient) {
63
84
  this.type = CHANGE_DOMAIN_TYPES.CLUSTER;
64
- this.namespace = { collection: '', database: 'admin' };
65
- this.cursorNamespace = this.namespace.database;
66
85
  this.topology = changeDomain.topology;
67
86
  } else {
68
87
  throw new TypeError(
@@ -75,16 +94,8 @@ class ChangeStream extends EventEmitter {
75
94
  this.options.readPreference = changeDomain.s.readPreference;
76
95
  }
77
96
 
78
- // We need to get the operationTime as early as possible
79
- const isMaster = this.topology.lastIsMaster();
80
- if (!isMaster) {
81
- throw new MongoError('Topology does not have an ismaster yet.');
82
- }
83
-
84
- this.operationTime = isMaster.operationTime;
85
-
86
97
  // Create contained Change Stream cursor
87
- this.cursor = createChangeStreamCursor(this);
98
+ this.cursor = createChangeStreamCursor(this, options);
88
99
 
89
100
  // Listen for any `change` listeners being added to ChangeStream
90
101
  this.on('newListener', eventName => {
@@ -103,6 +114,15 @@ class ChangeStream extends EventEmitter {
103
114
  });
104
115
  }
105
116
 
117
+ /**
118
+ * @property {ResumeToken} resumeToken
119
+ * The cached resume token that will be used to resume
120
+ * after the most recently returned change.
121
+ */
122
+ get resumeToken() {
123
+ return this.cursor.resumeToken;
124
+ }
125
+
106
126
  /**
107
127
  * Check if there is any document still available in the Change Stream
108
128
  * @function ChangeStream.prototype.hasNext
@@ -160,6 +180,7 @@ class ChangeStream extends EventEmitter {
160
180
 
161
181
  // Tidy up the existing cursor
162
182
  var cursor = this.cursor;
183
+ ['data', 'close', 'end', 'error'].forEach(event => this.cursor.removeAllListeners(event));
163
184
  delete this.cursor;
164
185
  return cursor.close(callback);
165
186
  }
@@ -220,13 +241,114 @@ class ChangeStream extends EventEmitter {
220
241
  }
221
242
  }
222
243
 
223
- // Create a new change stream cursor based on self's configuration
224
- var createChangeStreamCursor = function(self) {
225
- if (self.resumeToken) {
226
- self.options.resumeAfter = self.resumeToken;
244
+ class ChangeStreamCursor extends Cursor {
245
+ constructor(topology, ns, cmd, options) {
246
+ // TODO: spread will help a lot here
247
+ super(topology, ns, cmd, options);
248
+
249
+ options = options || {};
250
+ this._resumeToken = null;
251
+ this.startAtOperationTime = options.startAtOperationTime;
252
+
253
+ if (options.startAfter) {
254
+ this.resumeToken = options.startAfter;
255
+ } else if (options.resumeAfter) {
256
+ this.resumeToken = options.resumeAfter;
257
+ }
258
+ }
259
+
260
+ set resumeToken(token) {
261
+ this._resumeToken = token;
262
+ this.emit('resumeTokenChanged', token);
227
263
  }
228
264
 
229
- var changeStreamCursor = buildChangeStreamAggregationCommand(self);
265
+ get resumeToken() {
266
+ return this._resumeToken;
267
+ }
268
+
269
+ get resumeOptions() {
270
+ const result = {};
271
+ for (const optionName of CURSOR_OPTIONS) {
272
+ if (this.options[optionName]) result[optionName] = this.options[optionName];
273
+ }
274
+
275
+ if (this.resumeToken || this.startAtOperationTime) {
276
+ ['resumeAfter', 'startAfter', 'startAtOperationTime'].forEach(key => delete result[key]);
277
+
278
+ if (this.resumeToken) {
279
+ result.resumeAfter = this.resumeToken;
280
+ } else if (this.startAtOperationTime && maxWireVersion(this.server) >= 7) {
281
+ result.startAtOperationTime = this.startAtOperationTime;
282
+ }
283
+ }
284
+
285
+ return result;
286
+ }
287
+
288
+ _initializeCursor(callback) {
289
+ super._initializeCursor((err, result) => {
290
+ if (err) {
291
+ callback(err, null);
292
+ return;
293
+ }
294
+
295
+ const response = result.documents[0];
296
+
297
+ if (
298
+ this.startAtOperationTime == null &&
299
+ this.resumeAfter == null &&
300
+ this.startAfter == null &&
301
+ maxWireVersion(this.server) >= 7
302
+ ) {
303
+ this.startAtOperationTime = response.operationTime;
304
+ }
305
+
306
+ const cursor = response.cursor;
307
+ if (cursor.postBatchResumeToken) {
308
+ this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
309
+
310
+ if (cursor.firstBatch.length === 0) {
311
+ this.resumeToken = cursor.postBatchResumeToken;
312
+ }
313
+ }
314
+
315
+ this.emit('response');
316
+ callback(err, result);
317
+ });
318
+ }
319
+
320
+ _getMore(callback) {
321
+ super._getMore((err, response) => {
322
+ if (err) {
323
+ callback(err, null);
324
+ return;
325
+ }
326
+
327
+ const cursor = response.cursor;
328
+ if (cursor.postBatchResumeToken) {
329
+ this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
330
+
331
+ if (cursor.nextBatch.length === 0) {
332
+ this.resumeToken = cursor.postBatchResumeToken;
333
+ }
334
+ }
335
+
336
+ this.emit('response');
337
+ callback(err, response);
338
+ });
339
+ }
340
+ }
341
+
342
+ /**
343
+ * @event ChangeStreamCursor#response
344
+ * internal event DO NOT USE
345
+ * @ignore
346
+ */
347
+
348
+ // Create a new change stream cursor based on self's configuration
349
+ var createChangeStreamCursor = function(self, options) {
350
+ const changeStreamCursor = buildChangeStreamAggregationCommand(self, options);
351
+ relayEvents(changeStreamCursor, self, ['resumeTokenChanged', 'end', 'close']);
230
352
 
231
353
  /**
232
354
  * Fired for each new matching change in the specified namespace. Attaching a `change`
@@ -248,9 +370,6 @@ var createChangeStreamCursor = function(self) {
248
370
  * @event ChangeStream#close
249
371
  * @type {null}
250
372
  */
251
- changeStreamCursor.on('close', function() {
252
- self.emit('close');
253
- });
254
373
 
255
374
  /**
256
375
  * Change stream end event
@@ -258,9 +377,13 @@ var createChangeStreamCursor = function(self) {
258
377
  * @event ChangeStream#end
259
378
  * @type {null}
260
379
  */
261
- changeStreamCursor.on('end', function() {
262
- self.emit('end');
263
- });
380
+
381
+ /**
382
+ * Emitted each time the change stream stores a new resume token.
383
+ *
384
+ * @event ChangeStream#resumeTokenChanged
385
+ * @type {ResumeToken}
386
+ */
264
387
 
265
388
  /**
266
389
  * Fired when the stream encounters an error.
@@ -282,45 +405,26 @@ var createChangeStreamCursor = function(self) {
282
405
  return changeStreamCursor;
283
406
  };
284
407
 
285
- function getResumeToken(self) {
286
- return self.resumeToken || self.options.resumeAfter;
287
- }
288
-
289
- function getStartAtOperationTime(self) {
290
- const isMaster = self.topology.lastIsMaster() || {};
291
- return (
292
- isMaster.maxWireVersion && isMaster.maxWireVersion >= 7 && self.options.startAtOperationTime
293
- );
408
+ function applyKnownOptions(target, source, optionNames) {
409
+ optionNames.forEach(name => {
410
+ if (source[name]) {
411
+ target[name] = source[name];
412
+ }
413
+ });
294
414
  }
295
415
 
296
- var buildChangeStreamAggregationCommand = function(self) {
416
+ var buildChangeStreamAggregationCommand = function(self, options) {
417
+ options = options || {};
297
418
  const topology = self.topology;
298
419
  const namespace = self.namespace;
299
420
  const pipeline = self.pipeline;
300
- const options = self.options;
301
- const cursorNamespace = self.cursorNamespace;
302
421
 
303
- var changeStreamStageOptions = {
304
- fullDocument: options.fullDocument || 'default'
305
- };
306
-
307
- const resumeToken = getResumeToken(self);
308
- const startAtOperationTime = getStartAtOperationTime(self);
309
- if (resumeToken) {
310
- changeStreamStageOptions.resumeAfter = resumeToken;
311
- }
312
-
313
- if (startAtOperationTime) {
314
- changeStreamStageOptions.startAtOperationTime = startAtOperationTime;
315
- }
422
+ const changeStreamStageOptions = { fullDocument: options.fullDocument || 'default' };
423
+ applyKnownOptions(changeStreamStageOptions, options, CHANGE_STREAM_OPTIONS);
316
424
 
317
425
  // Map cursor options
318
- var cursorOptions = {};
319
- cursorOptionNames.forEach(function(optionName) {
320
- if (options[optionName]) {
321
- cursorOptions[optionName] = options[optionName];
322
- }
323
- });
426
+ const cursorOptions = { cursorFactory: ChangeStreamCursor };
427
+ applyKnownOptions(cursorOptions, options, CURSOR_OPTIONS);
324
428
 
325
429
  if (self.type === CHANGE_DOMAIN_TYPES.CLUSTER) {
326
430
  changeStreamStageOptions.allChangesForCluster = true;
@@ -333,14 +437,15 @@ var buildChangeStreamAggregationCommand = function(self) {
333
437
  var command = {
334
438
  aggregate: self.type === CHANGE_DOMAIN_TYPES.COLLECTION ? namespace.collection : 1,
335
439
  pipeline: changeStreamPipeline,
336
- readConcern: { level: 'majority' },
440
+ readConcern: new ReadConcern(ReadConcern.MAJORITY),
337
441
  cursor: {
338
442
  batchSize: options.batchSize || 1
339
443
  }
340
444
  };
341
445
 
342
446
  // Create and return the cursor
343
- return topology.cursor(cursorNamespace, command, cursorOptions);
447
+ // TODO: switch to passing namespace object later
448
+ return topology.cursor(namespace.toString(), command, cursorOptions);
344
449
  };
345
450
 
346
451
  // This method performs a basic server selection loop, satisfying the requirements of
@@ -382,6 +487,7 @@ function processNewChange(args) {
382
487
  : changeStream.promiseLibrary.reject(error);
383
488
  }
384
489
 
490
+ const cursor = changeStream.cursor;
385
491
  const topology = changeStream.topology;
386
492
  const options = changeStream.cursor.options;
387
493
 
@@ -389,11 +495,6 @@ function processNewChange(args) {
389
495
  if (isResumableError(error) && !changeStream.attemptingResume) {
390
496
  changeStream.attemptingResume = true;
391
497
 
392
- if (!(getResumeToken(changeStream) || getStartAtOperationTime(changeStream))) {
393
- const startAtOperationTime = changeStream.cursor.cursorState.operationTime;
394
- changeStream.options = Object.assign({ startAtOperationTime }, changeStream.options);
395
- }
396
-
397
498
  // stop listening to all events from old cursor
398
499
  ['data', 'close', 'end', 'error'].forEach(event =>
399
500
  changeStream.cursor.removeAllListeners(event)
@@ -406,7 +507,7 @@ function processNewChange(args) {
406
507
  if (eventEmitter) {
407
508
  waitForTopologyConnected(topology, { readPreference: options.readPreference }, err => {
408
509
  if (err) return changeStream.emit('error', err);
409
- changeStream.cursor = createChangeStreamCursor(changeStream);
510
+ changeStream.cursor = createChangeStreamCursor(changeStream, cursor.resumeOptions);
410
511
  });
411
512
 
412
513
  return;
@@ -416,7 +517,7 @@ function processNewChange(args) {
416
517
  waitForTopologyConnected(topology, { readPreference: options.readPreference }, err => {
417
518
  if (err) return callback(err, null);
418
519
 
419
- changeStream.cursor = createChangeStreamCursor(changeStream);
520
+ changeStream.cursor = createChangeStreamCursor(changeStream, cursor.resumeOptions);
420
521
  changeStream.next(callback);
421
522
  });
422
523
 
@@ -429,7 +530,9 @@ function processNewChange(args) {
429
530
  resolve();
430
531
  });
431
532
  })
432
- .then(() => (changeStream.cursor = createChangeStreamCursor(changeStream)))
533
+ .then(
534
+ () => (changeStream.cursor = createChangeStreamCursor(changeStream, cursor.resumeOptions))
535
+ )
433
536
  .then(() => changeStream.next());
434
537
  }
435
538
 
@@ -440,9 +543,8 @@ function processNewChange(args) {
440
543
 
441
544
  changeStream.attemptingResume = false;
442
545
 
443
- // Cache the resume token if it is present. If it is not present return an error.
444
- if (!change || !change._id) {
445
- var noResumeTokenError = new Error(
546
+ if (change && !change._id) {
547
+ const noResumeTokenError = new Error(
446
548
  'A change stream document has been received that lacks a resume token (_id).'
447
549
  );
448
550
 
@@ -451,7 +553,12 @@ function processNewChange(args) {
451
553
  return changeStream.promiseLibrary.reject(noResumeTokenError);
452
554
  }
453
555
 
454
- changeStream.resumeToken = change._id;
556
+ // cache the resume token
557
+ if (cursor.bufferedCount() === 0 && cursor.cursorState.postBatchResumeToken) {
558
+ cursor.resumeToken = cursor.cursorState.postBatchResumeToken;
559
+ } else {
560
+ cursor.resumeToken = change._id;
561
+ }
455
562
 
456
563
  // Return the change
457
564
  if (eventEmitter) return changeStream.emit('change', change);