mongodb 3.5.7 → 3.5.8
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.
- package/HISTORY.md +17 -0
- package/lib/bulk/common.js +4 -11
- package/lib/bulk/unordered.js +8 -0
- package/lib/change_stream.js +175 -139
- package/lib/cmap/connection_pool.js +11 -3
- package/lib/core/cursor.js +16 -20
- package/lib/core/error.js +11 -4
- package/lib/core/index.js +0 -1
- package/lib/core/sdam/server_selection.js +18 -36
- package/lib/core/utils.js +13 -11
- package/lib/cursor.js +10 -32
- package/lib/error.js +26 -33
- package/lib/operations/connect.js +6 -0
- package/lib/operations/cursor_ops.js +2 -3
- package/package.json +2 -2
package/HISTORY.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
<a name="3.5.8"></a>
|
|
6
|
+
## [3.5.8](https://github.com/mongodb/node-mongodb-native/compare/v3.5.7...v3.5.8) (2020-05-28)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* always clear cancelled wait queue members during processing ([0394f9d](https://github.com/mongodb/node-mongodb-native/commit/0394f9d))
|
|
12
|
+
* always include `writeErrors` on a `BulkWriteError` instance ([58b4f94](https://github.com/mongodb/node-mongodb-native/commit/58b4f94))
|
|
13
|
+
* ensure implicit sessions are ended consistently ([5c6fda1](https://github.com/mongodb/node-mongodb-native/commit/5c6fda1))
|
|
14
|
+
* filter servers before applying reducers ([4faf9f5](https://github.com/mongodb/node-mongodb-native/commit/4faf9f5))
|
|
15
|
+
* unordered bulk write should attempt to execute all batches ([6cee96b](https://github.com/mongodb/node-mongodb-native/commit/6cee96b))
|
|
16
|
+
* **ChangeStream:** should resume from errors when iterating ([5ecf18e](https://github.com/mongodb/node-mongodb-native/commit/5ecf18e))
|
|
17
|
+
* honor journal=true in connection string ([#2359](https://github.com/mongodb/node-mongodb-native/issues/2359)) ([246669f](https://github.com/mongodb/node-mongodb-native/commit/246669f))
|
|
18
|
+
* **ChangeStream:** whitelist resumable errors ([#2337](https://github.com/mongodb/node-mongodb-native/issues/2337)) ([a9d3965](https://github.com/mongodb/node-mongodb-native/commit/a9d3965)), closes [#17](https://github.com/mongodb/node-mongodb-native/issues/17) [#18](https://github.com/mongodb/node-mongodb-native/issues/18)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
5
22
|
<a name="3.5.7"></a>
|
|
6
23
|
## [3.5.7](https://github.com/mongodb/node-mongodb-native/compare/v3.5.6...v3.5.7) (2020-04-29)
|
|
7
24
|
|
package/lib/bulk/common.js
CHANGED
|
@@ -1200,19 +1200,10 @@ class BulkOperationBase {
|
|
|
1200
1200
|
* @ignore
|
|
1201
1201
|
* @param {function} callback
|
|
1202
1202
|
* @param {BulkWriteResult} writeResult
|
|
1203
|
-
* @param {class} self either OrderedBulkOperation or
|
|
1203
|
+
* @param {class} self either OrderedBulkOperation or UnorderedBulkOperation
|
|
1204
1204
|
*/
|
|
1205
1205
|
handleWriteError(callback, writeResult) {
|
|
1206
1206
|
if (this.s.bulkResult.writeErrors.length > 0) {
|
|
1207
|
-
if (this.s.bulkResult.writeErrors.length === 1) {
|
|
1208
|
-
handleCallback(
|
|
1209
|
-
callback,
|
|
1210
|
-
new BulkWriteError(toError(this.s.bulkResult.writeErrors[0]), writeResult),
|
|
1211
|
-
null
|
|
1212
|
-
);
|
|
1213
|
-
return true;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
1207
|
const msg = this.s.bulkResult.writeErrors[0].errmsg
|
|
1217
1208
|
? this.s.bulkResult.writeErrors[0].errmsg
|
|
1218
1209
|
: 'write operation failed';
|
|
@@ -1230,7 +1221,9 @@ class BulkOperationBase {
|
|
|
1230
1221
|
null
|
|
1231
1222
|
);
|
|
1232
1223
|
return true;
|
|
1233
|
-
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (writeResult.getWriteConcernError()) {
|
|
1234
1227
|
handleCallback(
|
|
1235
1228
|
callback,
|
|
1236
1229
|
new BulkWriteError(toError(writeResult.getWriteConcernError()), writeResult),
|
package/lib/bulk/unordered.js
CHANGED
|
@@ -108,6 +108,14 @@ class UnorderedBulkOperation extends BulkOperationBase {
|
|
|
108
108
|
|
|
109
109
|
super(topology, collection, options, false);
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
handleWriteError(callback, writeResult) {
|
|
113
|
+
if (this.s.batches.length) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return super.handleWriteError(callback, writeResult);
|
|
118
|
+
}
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
/**
|
package/lib/change_stream.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const Denque = require('denque');
|
|
3
4
|
const EventEmitter = require('events');
|
|
4
5
|
const isResumableError = require('./error').isResumableError;
|
|
5
6
|
const MongoError = require('./core').MongoError;
|
|
@@ -9,6 +10,8 @@ const maxWireVersion = require('./core/utils').maxWireVersion;
|
|
|
9
10
|
const maybePromise = require('./utils').maybePromise;
|
|
10
11
|
const AggregateOperation = require('./operations/aggregate');
|
|
11
12
|
|
|
13
|
+
const kResumeQueue = Symbol('resumeQueue');
|
|
14
|
+
|
|
12
15
|
const CHANGE_STREAM_OPTIONS = ['resumeAfter', 'startAfter', 'startAtOperationTime', 'fullDocument'];
|
|
13
16
|
const CURSOR_OPTIONS = ['batchSize', 'maxAwaitTimeMS', 'collation', 'readPreference'].concat(
|
|
14
17
|
CHANGE_STREAM_OPTIONS
|
|
@@ -91,15 +94,17 @@ class ChangeStream extends EventEmitter {
|
|
|
91
94
|
this.options.readPreference = parent.s.readPreference;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
this[kResumeQueue] = new Denque();
|
|
98
|
+
|
|
94
99
|
// Create contained Change Stream cursor
|
|
95
100
|
this.cursor = createChangeStreamCursor(this, options);
|
|
96
101
|
|
|
102
|
+
this.closed = false;
|
|
103
|
+
|
|
97
104
|
// Listen for any `change` listeners being added to ChangeStream
|
|
98
105
|
this.on('newListener', eventName => {
|
|
99
106
|
if (eventName === 'change' && this.cursor && this.listenerCount('change') === 0) {
|
|
100
|
-
this.cursor.on('data', change =>
|
|
101
|
-
processNewChange({ changeStream: this, change, eventEmitter: true })
|
|
102
|
-
);
|
|
107
|
+
this.cursor.on('data', change => processNewChange(this, change));
|
|
103
108
|
}
|
|
104
109
|
});
|
|
105
110
|
|
|
@@ -128,7 +133,12 @@ class ChangeStream extends EventEmitter {
|
|
|
128
133
|
* @returns {Promise|void} returns Promise if no callback passed
|
|
129
134
|
*/
|
|
130
135
|
hasNext(callback) {
|
|
131
|
-
return maybePromise(this.parent, callback, cb =>
|
|
136
|
+
return maybePromise(this.parent, callback, cb => {
|
|
137
|
+
getCursor(this, (err, cursor) => {
|
|
138
|
+
if (err) return cb(err); // failed to resume, raise an error
|
|
139
|
+
cursor.hasNext(cb);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
/**
|
|
@@ -140,25 +150,28 @@ class ChangeStream extends EventEmitter {
|
|
|
140
150
|
*/
|
|
141
151
|
next(callback) {
|
|
142
152
|
return maybePromise(this.parent, callback, cb => {
|
|
143
|
-
|
|
144
|
-
return cb(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
getCursor(this, (err, cursor) => {
|
|
154
|
+
if (err) return cb(err); // failed to resume, raise an error
|
|
155
|
+
cursor.next((error, change) => {
|
|
156
|
+
if (error) {
|
|
157
|
+
this[kResumeQueue].push(() => this.next(cb));
|
|
158
|
+
processError(this, error, cb);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
processNewChange(this, change, cb);
|
|
162
|
+
});
|
|
148
163
|
});
|
|
149
164
|
});
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
/**
|
|
153
|
-
* Is the
|
|
168
|
+
* Is the change stream closed
|
|
154
169
|
* @method ChangeStream.prototype.isClosed
|
|
170
|
+
* @param {boolean} [checkCursor=true] also check if the underlying cursor is closed
|
|
155
171
|
* @return {boolean}
|
|
156
172
|
*/
|
|
157
173
|
isClosed() {
|
|
158
|
-
|
|
159
|
-
return this.cursor.isClosed();
|
|
160
|
-
}
|
|
161
|
-
return true;
|
|
174
|
+
return this.closed || (this.cursor && this.cursor.isClosed());
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
/**
|
|
@@ -168,31 +181,22 @@ class ChangeStream extends EventEmitter {
|
|
|
168
181
|
* @return {Promise} returns Promise if no callback passed
|
|
169
182
|
*/
|
|
170
183
|
close(callback) {
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
return this.promiseLibrary.resolve();
|
|
174
|
-
}
|
|
184
|
+
return maybePromise(this.parent, callback, cb => {
|
|
185
|
+
if (this.closed) return cb();
|
|
175
186
|
|
|
176
|
-
|
|
177
|
-
|
|
187
|
+
// flag the change stream as explicitly closed
|
|
188
|
+
this.closed = true;
|
|
178
189
|
|
|
179
|
-
|
|
180
|
-
return cursor.close(err => {
|
|
181
|
-
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
182
|
-
delete this.cursor;
|
|
190
|
+
if (!this.cursor) return cb();
|
|
183
191
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
192
|
+
// Tidy up the existing cursor
|
|
193
|
+
const cursor = this.cursor;
|
|
187
194
|
|
|
188
|
-
|
|
189
|
-
return new PromiseCtor((resolve, reject) => {
|
|
190
|
-
cursor.close(err => {
|
|
195
|
+
return cursor.close(err => {
|
|
191
196
|
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
192
|
-
|
|
197
|
+
this.cursor = undefined;
|
|
193
198
|
|
|
194
|
-
|
|
195
|
-
resolve();
|
|
199
|
+
return cb(err);
|
|
196
200
|
});
|
|
197
201
|
});
|
|
198
202
|
}
|
|
@@ -287,7 +291,9 @@ class ChangeStreamCursor extends Cursor {
|
|
|
287
291
|
['resumeAfter', 'startAfter', 'startAtOperationTime'].forEach(key => delete result[key]);
|
|
288
292
|
|
|
289
293
|
if (this.resumeToken) {
|
|
290
|
-
|
|
294
|
+
const resumeKey =
|
|
295
|
+
this.options.startAfter && !this.hasReceived ? 'startAfter' : 'resumeAfter';
|
|
296
|
+
result[resumeKey] = this.resumeToken;
|
|
291
297
|
} else if (this.startAtOperationTime && maxWireVersion(this.server) >= 7) {
|
|
292
298
|
result.startAtOperationTime = this.startAtOperationTime;
|
|
293
299
|
}
|
|
@@ -296,10 +302,30 @@ class ChangeStreamCursor extends Cursor {
|
|
|
296
302
|
return result;
|
|
297
303
|
}
|
|
298
304
|
|
|
305
|
+
cacheResumeToken(resumeToken) {
|
|
306
|
+
if (this.bufferedCount() === 0 && this.cursorState.postBatchResumeToken) {
|
|
307
|
+
this.resumeToken = this.cursorState.postBatchResumeToken;
|
|
308
|
+
} else {
|
|
309
|
+
this.resumeToken = resumeToken;
|
|
310
|
+
}
|
|
311
|
+
this.hasReceived = true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_processBatch(batchName, response) {
|
|
315
|
+
const cursor = response.cursor;
|
|
316
|
+
if (cursor.postBatchResumeToken) {
|
|
317
|
+
this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
|
|
318
|
+
|
|
319
|
+
if (cursor[batchName].length === 0) {
|
|
320
|
+
this.resumeToken = cursor.postBatchResumeToken;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
299
325
|
_initializeCursor(callback) {
|
|
300
326
|
super._initializeCursor((err, result) => {
|
|
301
327
|
if (err) {
|
|
302
|
-
callback(err
|
|
328
|
+
callback(err);
|
|
303
329
|
return;
|
|
304
330
|
}
|
|
305
331
|
|
|
@@ -314,15 +340,9 @@ class ChangeStreamCursor extends Cursor {
|
|
|
314
340
|
this.startAtOperationTime = response.operationTime;
|
|
315
341
|
}
|
|
316
342
|
|
|
317
|
-
|
|
318
|
-
if (cursor.postBatchResumeToken) {
|
|
319
|
-
this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
|
|
320
|
-
|
|
321
|
-
if (cursor.firstBatch.length === 0) {
|
|
322
|
-
this.resumeToken = cursor.postBatchResumeToken;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
343
|
+
this._processBatch('firstBatch', response);
|
|
325
344
|
|
|
345
|
+
this.emit('init', result);
|
|
326
346
|
this.emit('response');
|
|
327
347
|
callback(err, result);
|
|
328
348
|
});
|
|
@@ -331,19 +351,13 @@ class ChangeStreamCursor extends Cursor {
|
|
|
331
351
|
_getMore(callback) {
|
|
332
352
|
super._getMore((err, response) => {
|
|
333
353
|
if (err) {
|
|
334
|
-
callback(err
|
|
354
|
+
callback(err);
|
|
335
355
|
return;
|
|
336
356
|
}
|
|
337
357
|
|
|
338
|
-
|
|
339
|
-
if (cursor.postBatchResumeToken) {
|
|
340
|
-
this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
|
|
341
|
-
|
|
342
|
-
if (cursor.nextBatch.length === 0) {
|
|
343
|
-
this.resumeToken = cursor.postBatchResumeToken;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
358
|
+
this._processBatch('nextBatch', response);
|
|
346
359
|
|
|
360
|
+
this.emit('more', response);
|
|
347
361
|
this.emit('response');
|
|
348
362
|
callback(err, response);
|
|
349
363
|
});
|
|
@@ -366,6 +380,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
366
380
|
|
|
367
381
|
const pipeline = [{ $changeStream: changeStreamStageOptions }].concat(self.pipeline);
|
|
368
382
|
const cursorOptions = applyKnownOptions({}, options, CURSOR_OPTIONS);
|
|
383
|
+
|
|
369
384
|
const changeStreamCursor = new ChangeStreamCursor(
|
|
370
385
|
self.topology,
|
|
371
386
|
new AggregateOperation(self.parent, pipeline, options),
|
|
@@ -384,7 +399,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
384
399
|
*/
|
|
385
400
|
if (self.listenerCount('change') > 0) {
|
|
386
401
|
changeStreamCursor.on('data', function(change) {
|
|
387
|
-
processNewChange(
|
|
402
|
+
processNewChange(self, change);
|
|
388
403
|
});
|
|
389
404
|
}
|
|
390
405
|
|
|
@@ -416,7 +431,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
416
431
|
* @type {Error}
|
|
417
432
|
*/
|
|
418
433
|
changeStreamCursor.on('error', function(error) {
|
|
419
|
-
|
|
434
|
+
processError(self, error);
|
|
420
435
|
});
|
|
421
436
|
|
|
422
437
|
if (self.pipeDestinations) {
|
|
@@ -449,120 +464,141 @@ function waitForTopologyConnected(topology, options, callback) {
|
|
|
449
464
|
const timeout = options.timeout || SELECTION_TIMEOUT;
|
|
450
465
|
const readPreference = options.readPreference;
|
|
451
466
|
|
|
452
|
-
if (topology.isConnected({ readPreference })) return callback(
|
|
467
|
+
if (topology.isConnected({ readPreference })) return callback();
|
|
453
468
|
const hrElapsed = process.hrtime(start);
|
|
454
469
|
const elapsed = (hrElapsed[0] * 1e9 + hrElapsed[1]) / 1e6;
|
|
455
470
|
if (elapsed > timeout) return callback(new MongoError('Timed out waiting for connection'));
|
|
456
471
|
waitForTopologyConnected(topology, options, callback);
|
|
457
|
-
},
|
|
472
|
+
}, 500); // this is an arbitrary wait time to allow SDAM to transition
|
|
458
473
|
}
|
|
459
474
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const changeStream = args.changeStream;
|
|
463
|
-
const error = args.error;
|
|
464
|
-
const change = args.change;
|
|
465
|
-
const callback = args.callback;
|
|
466
|
-
const eventEmitter = args.eventEmitter || false;
|
|
475
|
+
function processNewChange(changeStream, change, callback) {
|
|
476
|
+
const cursor = changeStream.cursor;
|
|
467
477
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
478
|
+
if (changeStream.closed) {
|
|
479
|
+
if (callback) callback(new MongoError('ChangeStream is closed'));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
474
482
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
483
|
+
if (change && !change._id) {
|
|
484
|
+
const noResumeTokenError = new Error(
|
|
485
|
+
'A change stream document has been received that lacks a resume token (_id).'
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
if (!callback) return changeStream.emit('error', noResumeTokenError);
|
|
489
|
+
return callback(noResumeTokenError);
|
|
479
490
|
}
|
|
480
491
|
|
|
481
|
-
|
|
492
|
+
// cache the resume token
|
|
493
|
+
cursor.cacheResumeToken(change._id);
|
|
494
|
+
|
|
495
|
+
// wipe the startAtOperationTime if there was one so that there won't be a conflict
|
|
496
|
+
// between resumeToken and startAtOperationTime if we need to reconnect the cursor
|
|
497
|
+
changeStream.options.startAtOperationTime = undefined;
|
|
498
|
+
|
|
499
|
+
// Return the change
|
|
500
|
+
if (!callback) return changeStream.emit('change', change);
|
|
501
|
+
return callback(undefined, change);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function processError(changeStream, error, callback) {
|
|
482
505
|
const topology = changeStream.topology;
|
|
483
|
-
const
|
|
506
|
+
const cursor = changeStream.cursor;
|
|
484
507
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
508
|
+
// If the change stream has been closed explictly, do not process error.
|
|
509
|
+
if (changeStream.closed) {
|
|
510
|
+
if (callback) callback(new MongoError('ChangeStream is closed'));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
488
513
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
514
|
+
// if the resume succeeds, continue with the new cursor
|
|
515
|
+
function resumeWithCursor(newCursor) {
|
|
516
|
+
changeStream.cursor = newCursor;
|
|
517
|
+
processResumeQueue(changeStream);
|
|
518
|
+
}
|
|
493
519
|
|
|
494
|
-
|
|
495
|
-
|
|
520
|
+
// otherwise, raise an error and close the change stream
|
|
521
|
+
function unresumableError(err) {
|
|
522
|
+
if (!callback) {
|
|
523
|
+
changeStream.emit('error', err);
|
|
524
|
+
changeStream.emit('close');
|
|
525
|
+
}
|
|
526
|
+
processResumeQueue(changeStream, err);
|
|
527
|
+
changeStream.closed = true;
|
|
528
|
+
}
|
|
496
529
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
waitForTopologyConnected(topology, { readPreference: options.readPreference }, err => {
|
|
500
|
-
if (err) {
|
|
501
|
-
changeStream.emit('error', err);
|
|
502
|
-
changeStream.emit('close');
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
changeStream.cursor = createChangeStreamCursor(changeStream, cursor.resumeOptions);
|
|
506
|
-
});
|
|
530
|
+
if (cursor && isResumableError(error, maxWireVersion(cursor.server))) {
|
|
531
|
+
changeStream.cursor = undefined;
|
|
507
532
|
|
|
508
|
-
|
|
509
|
-
|
|
533
|
+
// stop listening to all events from old cursor
|
|
534
|
+
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
510
535
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if (err) return callback(err, null);
|
|
536
|
+
// close internal cursor, ignore errors
|
|
537
|
+
cursor.close();
|
|
514
538
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
539
|
+
waitForTopologyConnected(topology, { readPreference: cursor.options.readPreference }, err => {
|
|
540
|
+
// if the topology can't reconnect, close the stream
|
|
541
|
+
if (err) return unresumableError(err);
|
|
518
542
|
|
|
519
|
-
|
|
520
|
-
|
|
543
|
+
// create a new cursor, preserving the old cursor's options
|
|
544
|
+
const newCursor = createChangeStreamCursor(changeStream, cursor.resumeOptions);
|
|
521
545
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (err) return reject(err);
|
|
525
|
-
resolve();
|
|
526
|
-
});
|
|
527
|
-
})
|
|
528
|
-
.then(
|
|
529
|
-
() => (changeStream.cursor = createChangeStreamCursor(changeStream, cursor.resumeOptions))
|
|
530
|
-
)
|
|
531
|
-
.then(() => changeStream.next());
|
|
532
|
-
}
|
|
546
|
+
// attempt to continue in emitter mode
|
|
547
|
+
if (!callback) return resumeWithCursor(newCursor);
|
|
533
548
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
549
|
+
// attempt to continue in iterator mode
|
|
550
|
+
newCursor.hasNext(err => {
|
|
551
|
+
// if there's an error immediately after resuming, close the stream
|
|
552
|
+
if (err) return unresumableError(err);
|
|
553
|
+
resumeWithCursor(newCursor);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
return;
|
|
537
557
|
}
|
|
538
558
|
|
|
539
|
-
changeStream.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const noResumeTokenError = new Error(
|
|
543
|
-
'A change stream document has been received that lacks a resume token (_id).'
|
|
544
|
-
);
|
|
559
|
+
if (!callback) return changeStream.emit('error', error);
|
|
560
|
+
return callback(error);
|
|
561
|
+
}
|
|
545
562
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
563
|
+
/**
|
|
564
|
+
* Safely provides a cursor across resume attempts
|
|
565
|
+
*
|
|
566
|
+
* @param {ChangeStream} changeStream the parent ChangeStream
|
|
567
|
+
* @param {function} callback gets the cursor or error
|
|
568
|
+
* @param {ChangeStreamCursor} [oldCursor] when resuming from an error, carry over options from previous cursor
|
|
569
|
+
*/
|
|
570
|
+
function getCursor(changeStream, callback) {
|
|
571
|
+
if (changeStream.isClosed()) {
|
|
572
|
+
callback(new MongoError('ChangeStream is closed.'));
|
|
573
|
+
return;
|
|
549
574
|
}
|
|
550
575
|
|
|
551
|
-
//
|
|
552
|
-
if (
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
cursor.resumeToken = change._id;
|
|
576
|
+
// if a cursor exists and it is open, return it
|
|
577
|
+
if (changeStream.cursor) {
|
|
578
|
+
callback(undefined, changeStream.cursor);
|
|
579
|
+
return;
|
|
556
580
|
}
|
|
557
581
|
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
|
|
582
|
+
// no cursor, queue callback until topology reconnects
|
|
583
|
+
changeStream[kResumeQueue].push(callback);
|
|
584
|
+
}
|
|
561
585
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
586
|
+
/**
|
|
587
|
+
* Drain the resume queue when a new has become available
|
|
588
|
+
*
|
|
589
|
+
* @param {ChangeStream} changeStream the parent ChangeStream
|
|
590
|
+
* @param {ChangeStreamCursor?} changeStream.cursor the new cursor
|
|
591
|
+
* @param {Error} [err] error getting a new cursor
|
|
592
|
+
*/
|
|
593
|
+
function processResumeQueue(changeStream, err) {
|
|
594
|
+
while (changeStream[kResumeQueue].length) {
|
|
595
|
+
const request = changeStream[kResumeQueue].pop();
|
|
596
|
+
if (changeStream.isClosed() && !err) {
|
|
597
|
+
request(new MongoError('Change Stream is not open.'));
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
request(err, changeStream.cursor);
|
|
601
|
+
}
|
|
566
602
|
}
|
|
567
603
|
|
|
568
604
|
/**
|
|
@@ -198,6 +198,10 @@ class ConnectionPool extends EventEmitter {
|
|
|
198
198
|
return this[kConnections].length;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
get waitQueueSize() {
|
|
202
|
+
return this[kWaitQueue].length;
|
|
203
|
+
}
|
|
204
|
+
|
|
201
205
|
/**
|
|
202
206
|
* Check a connection out of this pool. The connection will continue to be tracked, but no reference to it
|
|
203
207
|
* will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
|
|
@@ -295,7 +299,7 @@ class ConnectionPool extends EventEmitter {
|
|
|
295
299
|
this[kCancellationToken].emit('cancel');
|
|
296
300
|
|
|
297
301
|
// drain the wait queue
|
|
298
|
-
while (this
|
|
302
|
+
while (this.waitQueueSize) {
|
|
299
303
|
const waitQueueMember = this[kWaitQueue].pop();
|
|
300
304
|
clearTimeout(waitQueueMember.timer);
|
|
301
305
|
if (!waitQueueMember[kCancelled]) {
|
|
@@ -449,13 +453,17 @@ function processWaitQueue(pool) {
|
|
|
449
453
|
return;
|
|
450
454
|
}
|
|
451
455
|
|
|
452
|
-
while (pool
|
|
456
|
+
while (pool.waitQueueSize) {
|
|
453
457
|
const waitQueueMember = pool[kWaitQueue].peekFront();
|
|
454
458
|
if (waitQueueMember[kCancelled]) {
|
|
455
459
|
pool[kWaitQueue].shift();
|
|
456
460
|
continue;
|
|
457
461
|
}
|
|
458
462
|
|
|
463
|
+
if (!pool.availableConnectionCount) {
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
|
|
459
467
|
const connection = pool[kConnections].shift();
|
|
460
468
|
const isStale = connectionIsStale(pool, connection);
|
|
461
469
|
const isIdle = connectionIsIdle(pool, connection);
|
|
@@ -472,7 +480,7 @@ function processWaitQueue(pool) {
|
|
|
472
480
|
}
|
|
473
481
|
|
|
474
482
|
const maxPoolSize = pool.options.maxPoolSize;
|
|
475
|
-
if (pool
|
|
483
|
+
if (pool.waitQueueSize && (maxPoolSize <= 0 || pool.totalConnectionCount < maxPoolSize)) {
|
|
476
484
|
createConnection(pool, (err, connection) => {
|
|
477
485
|
const waitQueueMember = pool[kWaitQueue].shift();
|
|
478
486
|
if (waitQueueMember == null) {
|
package/lib/core/cursor.js
CHANGED
|
@@ -4,7 +4,6 @@ const Logger = require('./connection/logger');
|
|
|
4
4
|
const retrieveBSON = require('./connection/utils').retrieveBSON;
|
|
5
5
|
const MongoError = require('./error').MongoError;
|
|
6
6
|
const MongoNetworkError = require('./error').MongoNetworkError;
|
|
7
|
-
const mongoErrorContextSymbol = require('./error').mongoErrorContextSymbol;
|
|
8
7
|
const collationNotSupported = require('./utils').collationNotSupported;
|
|
9
8
|
const ReadPreference = require('./topologies/read_preference');
|
|
10
9
|
const isUnifiedTopology = require('./utils').isUnifiedTopology;
|
|
@@ -412,7 +411,15 @@ class CoreCursor extends Readable {
|
|
|
412
411
|
batchSize = this.cursorState.limit - this.cursorState.currentLimit;
|
|
413
412
|
}
|
|
414
413
|
|
|
415
|
-
|
|
414
|
+
const cursorState = this.cursorState;
|
|
415
|
+
this.server.getMore(this.ns, cursorState, batchSize, this.options, (err, result, conn) => {
|
|
416
|
+
// NOTE: `getMore` modifies `cursorState`, would be very ideal not to do so in the future
|
|
417
|
+
if (err || (cursorState.cursorId && cursorState.cursorId.isZero())) {
|
|
418
|
+
this._endSession();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
callback(err, result, conn);
|
|
422
|
+
});
|
|
416
423
|
}
|
|
417
424
|
|
|
418
425
|
_initializeCursor(callback) {
|
|
@@ -433,18 +440,15 @@ class CoreCursor extends Readable {
|
|
|
433
440
|
}
|
|
434
441
|
|
|
435
442
|
function done(err, result) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
cursor.cursorState.cursorId.isZero() &&
|
|
439
|
-
cursor._endSession
|
|
440
|
-
) {
|
|
443
|
+
const cursorState = cursor.cursorState;
|
|
444
|
+
if (err || (cursorState.cursorId && cursorState.cursorId.isZero())) {
|
|
441
445
|
cursor._endSession();
|
|
442
446
|
}
|
|
443
447
|
|
|
444
448
|
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
449
|
+
cursorState.documents.length === 0 &&
|
|
450
|
+
cursorState.cursorId &&
|
|
451
|
+
cursorState.cursorId.isZero() &&
|
|
448
452
|
!cursor.cmd.tailable &&
|
|
449
453
|
!cursor.cmd.awaitData
|
|
450
454
|
) {
|
|
@@ -690,8 +694,8 @@ function _setCursorNotifiedImpl(self, callback) {
|
|
|
690
694
|
self.cursorState.documents = [];
|
|
691
695
|
self.cursorState.cursorIndex = 0;
|
|
692
696
|
|
|
693
|
-
if (self.
|
|
694
|
-
self._endSession(
|
|
697
|
+
if (self.cursorState.session) {
|
|
698
|
+
self._endSession(callback);
|
|
695
699
|
return;
|
|
696
700
|
}
|
|
697
701
|
|
|
@@ -774,17 +778,9 @@ function nextFunction(self, callback) {
|
|
|
774
778
|
// Execute the next get more
|
|
775
779
|
self._getMore(function(err, doc, connection) {
|
|
776
780
|
if (err) {
|
|
777
|
-
if (err instanceof MongoError) {
|
|
778
|
-
err[mongoErrorContextSymbol].isGetMore = true;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
781
|
return handleCallback(callback, err);
|
|
782
782
|
}
|
|
783
783
|
|
|
784
|
-
if (self.cursorState.cursorId && self.cursorState.cursorId.isZero() && self._endSession) {
|
|
785
|
-
self._endSession();
|
|
786
|
-
}
|
|
787
|
-
|
|
788
784
|
// Save the returned connection to ensure all getMore's fire over the same connection
|
|
789
785
|
self.connection = connection;
|
|
790
786
|
|
package/lib/core/error.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const mongoErrorContextSymbol = Symbol('mongoErrorContextSymbol');
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Creates a new MongoError
|
|
7
5
|
*
|
|
@@ -21,6 +19,10 @@ class MongoError extends Error {
|
|
|
21
19
|
} else {
|
|
22
20
|
super(message.message || message.errmsg || message.$err || 'n/a');
|
|
23
21
|
for (var name in message) {
|
|
22
|
+
if (name === 'errmsg') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
24
26
|
this[name] = message[name];
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -29,7 +31,13 @@ class MongoError extends Error {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
this.name = 'MongoError';
|
|
32
|
-
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Legacy name for server error responses
|
|
38
|
+
*/
|
|
39
|
+
get errmsg() {
|
|
40
|
+
return this.message;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
/**
|
|
@@ -262,7 +270,6 @@ module.exports = {
|
|
|
262
270
|
MongoTimeoutError,
|
|
263
271
|
MongoServerSelectionError,
|
|
264
272
|
MongoWriteConcernError,
|
|
265
|
-
mongoErrorContextSymbol,
|
|
266
273
|
isRetryableError,
|
|
267
274
|
isSDAMUnrecoverableError,
|
|
268
275
|
isNodeShuttingDownError,
|
package/lib/core/index.js
CHANGED
|
@@ -22,7 +22,6 @@ module.exports = {
|
|
|
22
22
|
MongoTimeoutError: require('./error').MongoTimeoutError,
|
|
23
23
|
MongoServerSelectionError: require('./error').MongoServerSelectionError,
|
|
24
24
|
MongoWriteConcernError: require('./error').MongoWriteConcernError,
|
|
25
|
-
mongoErrorContextSymbol: require('./error').mongoErrorContextSymbol,
|
|
26
25
|
// Core
|
|
27
26
|
Connection: require('./connection/connection'),
|
|
28
27
|
Server: require('./topologies/server'),
|
|
@@ -48,7 +48,7 @@ function maxStalenessReducer(readPreference, topologyDescription, servers) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (topologyDescription.type === TopologyType.ReplicaSetWithPrimary) {
|
|
51
|
-
const primary = servers.filter(primaryFilter)[0];
|
|
51
|
+
const primary = Array.from(topologyDescription.servers.values()).filter(primaryFilter)[0];
|
|
52
52
|
return servers.reduce((result, server) => {
|
|
53
53
|
const stalenessMS =
|
|
54
54
|
server.lastUpdateTime -
|
|
@@ -197,50 +197,32 @@ function readPreferenceServerSelector(readPreference) {
|
|
|
197
197
|
return latencyWindowReducer(topologyDescription, servers.filter(knownFilter));
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
const mode = readPreference.mode;
|
|
201
|
+
if (mode === ReadPreference.PRIMARY) {
|
|
201
202
|
return servers.filter(primaryFilter);
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
if (
|
|
205
|
-
return latencyWindowReducer(
|
|
206
|
-
topologyDescription,
|
|
207
|
-
tagSetReducer(
|
|
208
|
-
readPreference,
|
|
209
|
-
maxStalenessReducer(readPreference, topologyDescription, servers)
|
|
210
|
-
)
|
|
211
|
-
).filter(secondaryFilter);
|
|
212
|
-
} else if (readPreference.mode === ReadPreference.NEAREST) {
|
|
213
|
-
return latencyWindowReducer(
|
|
214
|
-
topologyDescription,
|
|
215
|
-
tagSetReducer(
|
|
216
|
-
readPreference,
|
|
217
|
-
maxStalenessReducer(readPreference, topologyDescription, servers)
|
|
218
|
-
)
|
|
219
|
-
).filter(nearestFilter);
|
|
220
|
-
} else if (readPreference.mode === ReadPreference.SECONDARY_PREFERRED) {
|
|
221
|
-
const result = latencyWindowReducer(
|
|
222
|
-
topologyDescription,
|
|
223
|
-
tagSetReducer(
|
|
224
|
-
readPreference,
|
|
225
|
-
maxStalenessReducer(readPreference, topologyDescription, servers)
|
|
226
|
-
)
|
|
227
|
-
).filter(secondaryFilter);
|
|
228
|
-
|
|
229
|
-
return result.length === 0 ? servers.filter(primaryFilter) : result;
|
|
230
|
-
} else if (readPreference.mode === ReadPreference.PRIMARY_PREFERRED) {
|
|
205
|
+
if (mode === ReadPreference.PRIMARY_PREFERRED) {
|
|
231
206
|
const result = servers.filter(primaryFilter);
|
|
232
207
|
if (result.length) {
|
|
233
208
|
return result;
|
|
234
209
|
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const filter = mode === ReadPreference.NEAREST ? nearestFilter : secondaryFilter;
|
|
213
|
+
const selectedServers = latencyWindowReducer(
|
|
214
|
+
topologyDescription,
|
|
215
|
+
tagSetReducer(
|
|
216
|
+
readPreference,
|
|
217
|
+
maxStalenessReducer(readPreference, topologyDescription, servers.filter(filter))
|
|
218
|
+
)
|
|
219
|
+
);
|
|
235
220
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
tagSetReducer(
|
|
239
|
-
readPreference,
|
|
240
|
-
maxStalenessReducer(readPreference, topologyDescription, servers)
|
|
241
|
-
)
|
|
242
|
-
).filter(secondaryFilter);
|
|
221
|
+
if (mode === ReadPreference.SECONDARY_PREFERRED && selectedServers.length === 0) {
|
|
222
|
+
return servers.filter(primaryFilter);
|
|
243
223
|
}
|
|
224
|
+
|
|
225
|
+
return selectedServers;
|
|
244
226
|
};
|
|
245
227
|
}
|
|
246
228
|
|
package/lib/core/utils.js
CHANGED
|
@@ -83,22 +83,24 @@ function retrieveEJSON() {
|
|
|
83
83
|
* @param {(Topology|Server)} topologyOrServer
|
|
84
84
|
*/
|
|
85
85
|
function maxWireVersion(topologyOrServer) {
|
|
86
|
-
if (topologyOrServer
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
if (topologyOrServer) {
|
|
87
|
+
if (topologyOrServer.ismaster) {
|
|
88
|
+
return topologyOrServer.ismaster.maxWireVersion;
|
|
89
|
+
}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
if (typeof topologyOrServer.lastIsMaster === 'function') {
|
|
92
|
+
const lastIsMaster = topologyOrServer.lastIsMaster();
|
|
93
|
+
if (lastIsMaster) {
|
|
94
|
+
return lastIsMaster.maxWireVersion;
|
|
95
|
+
}
|
|
94
96
|
}
|
|
95
|
-
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
if (topologyOrServer.description) {
|
|
99
|
+
return topologyOrServer.description.maxWireVersion;
|
|
100
|
+
}
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
return
|
|
103
|
+
return 0;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/*
|
package/lib/cursor.js
CHANGED
|
@@ -832,9 +832,7 @@ class Cursor extends CoreCursor {
|
|
|
832
832
|
const fetchDocs = () => {
|
|
833
833
|
cursor._next((err, doc) => {
|
|
834
834
|
if (err) {
|
|
835
|
-
return
|
|
836
|
-
? cursor._endSession(() => handleCallback(cb, err))
|
|
837
|
-
: handleCallback(cb, err);
|
|
835
|
+
return handleCallback(cb, err);
|
|
838
836
|
}
|
|
839
837
|
|
|
840
838
|
if (doc == null) {
|
|
@@ -914,38 +912,18 @@ class Cursor extends CoreCursor {
|
|
|
914
912
|
if (typeof options === 'function') (callback = options), (options = {});
|
|
915
913
|
options = Object.assign({}, { skipKillCursors: false }, options);
|
|
916
914
|
|
|
917
|
-
this
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const completeClose = () => {
|
|
924
|
-
// Emit the close event for the cursor
|
|
925
|
-
this.emit('close');
|
|
926
|
-
|
|
927
|
-
// Callback if provided
|
|
928
|
-
if (typeof callback === 'function') {
|
|
929
|
-
return handleCallback(callback, null, this);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// Return a Promise
|
|
933
|
-
return new this.s.promiseLibrary(resolve => {
|
|
934
|
-
resolve();
|
|
935
|
-
});
|
|
936
|
-
};
|
|
937
|
-
|
|
938
|
-
if (this.cursorState.session) {
|
|
939
|
-
if (typeof callback === 'function') {
|
|
940
|
-
return this._endSession(() => completeClose());
|
|
915
|
+
return maybePromise(this, callback, cb => {
|
|
916
|
+
this.s.state = CursorState.CLOSED;
|
|
917
|
+
if (!options.skipKillCursors) {
|
|
918
|
+
// Kill the cursor
|
|
919
|
+
this.kill();
|
|
941
920
|
}
|
|
942
921
|
|
|
943
|
-
|
|
944
|
-
this.
|
|
922
|
+
this._endSession(() => {
|
|
923
|
+
this.emit('close');
|
|
924
|
+
cb(null, this);
|
|
945
925
|
});
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
return completeClose();
|
|
926
|
+
});
|
|
949
927
|
}
|
|
950
928
|
|
|
951
929
|
/**
|
package/lib/error.js
CHANGED
|
@@ -1,45 +1,38 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const MongoNetworkError = require('./core').MongoNetworkError;
|
|
4
|
-
const mongoErrorContextSymbol = require('./core').mongoErrorContextSymbol;
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
// From spec@https://github.com/mongodb/specifications/blob/f93d78191f3db2898a59013a7ed5650352ef6da8/source/change-streams/change-streams.rst#resumable-error
|
|
6
|
+
const GET_MORE_RESUMABLE_CODES = new Set([
|
|
7
|
+
6, // HostUnreachable
|
|
8
|
+
7, // HostNotFound
|
|
9
|
+
89, // NetworkTimeout
|
|
10
|
+
91, // ShutdownInProgress
|
|
11
|
+
189, // PrimarySteppedDown
|
|
12
|
+
262, // ExceededTimeLimit
|
|
13
|
+
9001, // SocketException
|
|
14
|
+
10107, // NotMaster
|
|
15
|
+
11600, // InterruptedAtShutdown
|
|
16
|
+
11602, // InterruptedDueToReplStateChange
|
|
17
|
+
13435, // NotMasterNoSlaveOk
|
|
18
|
+
13436, // NotMasterOrSecondary
|
|
19
|
+
63, // StaleShardVersion
|
|
20
|
+
150, // StaleEpoch
|
|
21
|
+
13388, // StaleConfig
|
|
22
|
+
234, // RetryChangeStream
|
|
23
|
+
133 // FailedToSatisfyReadPreference
|
|
10
24
|
]);
|
|
11
25
|
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
// An error is considered resumable if it meets any of the following criteria:
|
|
15
|
-
// - any error encountered which is not a server error (e.g. a timeout error or network error)
|
|
16
|
-
// - any server error response from a getMore command excluding those containing the error label
|
|
17
|
-
// NonRetryableChangeStreamError and those containing the following error codes:
|
|
18
|
-
// - Interrupted: 11601
|
|
19
|
-
// - CappedPositionLost: 136
|
|
20
|
-
// - CursorKilled: 237
|
|
21
|
-
//
|
|
22
|
-
// An error on an aggregate command is not a resumable error. Only errors on a getMore command may be considered resumable errors.
|
|
23
|
-
|
|
24
|
-
function isGetMoreError(error) {
|
|
25
|
-
if (error[mongoErrorContextSymbol]) {
|
|
26
|
-
return error[mongoErrorContextSymbol].isGetMore;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isResumableError(error) {
|
|
31
|
-
if (!isGetMoreError(error)) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
26
|
+
function isResumableError(error, wireVersion) {
|
|
35
27
|
if (error instanceof MongoNetworkError) {
|
|
36
28
|
return true;
|
|
37
29
|
}
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
if (wireVersion >= 9) {
|
|
32
|
+
return error.hasErrorLabel('ResumableChangeStreamError');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return GET_MORE_RESUMABLE_CODES.has(error.code);
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
module.exports = {
|
|
38
|
+
module.exports = { GET_MORE_RESUMABLE_CODES, isResumableError };
|
|
@@ -290,6 +290,12 @@ function connect(mongoClient, url, options, callback) {
|
|
|
290
290
|
delete _finalOptions.db_options.auth;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
// `journal` should be translated to `j` for the driver
|
|
294
|
+
if (_finalOptions.journal != null) {
|
|
295
|
+
_finalOptions.j = _finalOptions.journal;
|
|
296
|
+
_finalOptions.journal = undefined;
|
|
297
|
+
}
|
|
298
|
+
|
|
293
299
|
// resolve tls options if needed
|
|
294
300
|
resolveTLSOptions(_finalOptions);
|
|
295
301
|
|
|
@@ -134,10 +134,9 @@ function toArray(cursor, callback) {
|
|
|
134
134
|
const fetchDocs = () => {
|
|
135
135
|
cursor._next((err, doc) => {
|
|
136
136
|
if (err) {
|
|
137
|
-
return
|
|
138
|
-
? cursor._endSession(() => handleCallback(callback, err))
|
|
139
|
-
: handleCallback(callback, err);
|
|
137
|
+
return handleCallback(callback, err);
|
|
140
138
|
}
|
|
139
|
+
|
|
141
140
|
if (doc == null) {
|
|
142
141
|
return cursor.close({ skipKillCursors: true }, () => handleCallback(callback, null, items));
|
|
143
142
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongodb",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.8",
|
|
4
4
|
"description": "The official MongoDB driver for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"test": "npm run lint && mocha --recursive test/functional test/unit test/core",
|
|
69
69
|
"test-nolint": "mocha --recursive test/functional test/unit test/core",
|
|
70
70
|
"coverage": "istanbul cover mongodb-test-runner -- -t 60000 test/core test/unit test/functional",
|
|
71
|
-
"lint": "eslint lib test",
|
|
71
|
+
"lint": "eslint -v && eslint lib test",
|
|
72
72
|
"format": "prettier --print-width 100 --tab-width 2 --single-quote --write 'test/**/*.js' 'lib/**/*.js'",
|
|
73
73
|
"bench": "node test/benchmarks/driverBench/",
|
|
74
74
|
"generate-evergreen": "node .evergreen/generate_evergreen_tasks.js",
|