mongodb 3.6.12 → 3.7.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.
package/index.js CHANGED
@@ -17,6 +17,9 @@ connect.MongoWriteConcernError = core.MongoWriteConcernError;
17
17
  connect.MongoBulkWriteError = require('./lib/bulk/common').BulkWriteError;
18
18
  connect.BulkWriteError = connect.MongoBulkWriteError;
19
19
 
20
+ // Expose server versions
21
+ connect.ServerApiVersion = core.ServerApiVersion;
22
+
20
23
  // Actual driver classes exported
21
24
  connect.Admin = require('./lib/admin');
22
25
  connect.MongoClient = require('./lib/mongo_client');
@@ -47,6 +50,7 @@ connect.Int32 = core.BSON.Int32;
47
50
  connect.Long = core.BSON.Long;
48
51
  connect.MinKey = core.BSON.MinKey;
49
52
  connect.MaxKey = core.BSON.MaxKey;
53
+ /** @deprecated Please use `ObjectId` */
50
54
  connect.ObjectID = core.BSON.ObjectID;
51
55
  connect.ObjectId = core.BSON.ObjectID;
52
56
  connect.Symbol = core.BSON.Symbol;
@@ -37,6 +37,8 @@ class Connection extends EventEmitter {
37
37
  this.port = options.port || 27017;
38
38
  this.monitorCommands =
39
39
  typeof options.monitorCommands === 'boolean' ? options.monitorCommands : false;
40
+ this.serverApi = options.serverApi;
41
+
40
42
  this.closed = false;
41
43
  this.destroyed = false;
42
44
 
@@ -170,33 +172,58 @@ class Connection extends EventEmitter {
170
172
  });
171
173
  }
172
174
 
175
+ applyApiVersion(options) {
176
+ if (this.serverApi) {
177
+ options.serverApi = this.serverApi;
178
+ }
179
+ return options;
180
+ }
181
+
173
182
  // Wire protocol methods
174
183
  command(ns, cmd, options, callback) {
175
- wp.command(makeServerTrampoline(this), ns, cmd, options, callback);
184
+ if (typeof options === 'function') {
185
+ callback = options;
186
+ options = {};
187
+ }
188
+ wp.command(makeServerTrampoline(this), ns, cmd, this.applyApiVersion(options), callback);
176
189
  }
177
190
 
178
191
  query(ns, cmd, cursorState, options, callback) {
179
- wp.query(makeServerTrampoline(this), ns, cmd, cursorState, options, callback);
192
+ wp.query(
193
+ makeServerTrampoline(this),
194
+ ns,
195
+ cmd,
196
+ cursorState,
197
+ this.applyApiVersion(options),
198
+ callback
199
+ );
180
200
  }
181
201
 
182
202
  getMore(ns, cursorState, batchSize, options, callback) {
183
- wp.getMore(makeServerTrampoline(this), ns, cursorState, batchSize, options, callback);
203
+ wp.getMore(
204
+ makeServerTrampoline(this),
205
+ ns,
206
+ cursorState,
207
+ batchSize,
208
+ this.applyApiVersion(options),
209
+ callback
210
+ );
184
211
  }
185
212
 
186
213
  killCursors(ns, cursorState, callback) {
187
- wp.killCursors(makeServerTrampoline(this), ns, cursorState, callback);
214
+ wp.killCursors(makeServerTrampoline(this), ns, cursorState, this.applyApiVersion({}), callback);
188
215
  }
189
216
 
190
217
  insert(ns, ops, options, callback) {
191
- wp.insert(makeServerTrampoline(this), ns, ops, options, callback);
218
+ wp.insert(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback);
192
219
  }
193
220
 
194
221
  update(ns, ops, options, callback) {
195
- wp.update(makeServerTrampoline(this), ns, ops, options, callback);
222
+ wp.update(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback);
196
223
  }
197
224
 
198
225
  remove(ns, ops, options, callback) {
199
- wp.remove(makeServerTrampoline(this), ns, ops, options, callback);
226
+ wp.remove(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback);
200
227
  }
201
228
  }
202
229
 
@@ -41,6 +41,7 @@ const VALID_POOL_OPTIONS = new Set([
41
41
  'ssl',
42
42
  'bson',
43
43
  'connectionType',
44
+ 'serverApi',
44
45
  'monitorCommands',
45
46
  'socketTimeout',
46
47
  'credentials',
package/lib/collection.js CHANGED
@@ -1532,7 +1532,7 @@ Collection.prototype.count = deprecate(function(query, options, callback) {
1532
1532
 
1533
1533
  return executeOperation(
1534
1534
  this.s.topology,
1535
- new EstimatedDocumentCountOperation(this, query, options),
1535
+ new EstimatedDocumentCountOperation(this, Object.assign({ query }, options)),
1536
1536
  callback
1537
1537
  );
1538
1538
  }, 'collection.count is deprecated, and will be removed in a future version.' +
@@ -108,6 +108,11 @@ function performInitialHandshake(conn, options, _callback) {
108
108
  return;
109
109
  }
110
110
 
111
+ if ('isWritablePrimary' in response) {
112
+ // Provide pre-hello-style response document.
113
+ response.ismaster = response.isWritablePrimary;
114
+ }
115
+
111
116
  const supportedServerErr = checkSupportedServer(response, options);
112
117
  if (supportedServerErr) {
113
118
  callback(supportedServerErr);
@@ -158,11 +163,12 @@ function performInitialHandshake(conn, options, _callback) {
158
163
 
159
164
  function prepareHandshakeDocument(authContext, callback) {
160
165
  const options = authContext.options;
166
+ const serverApi = authContext.connection.serverApi;
161
167
  const compressors =
162
168
  options.compression && options.compression.compressors ? options.compression.compressors : [];
163
169
 
164
170
  const handshakeDoc = {
165
- ismaster: true,
171
+ [serverApi ? 'hello' : 'ismaster']: true,
166
172
  client: options.metadata || makeClientMetadata(options),
167
173
  compression: compressors
168
174
  };
package/lib/core/index.js CHANGED
@@ -15,7 +15,16 @@ try {
15
15
  }
16
16
  } catch (err) {} // eslint-disable-line
17
17
 
18
+ /** An enumeration of valid server API versions */
19
+ const ServerApiVersion = Object.freeze({
20
+ v1: '1'
21
+ });
22
+ const ValidServerApiVersions = Object.keys(ServerApiVersion).map(key => ServerApiVersion[key]);
23
+
18
24
  module.exports = {
25
+ // Versioned API
26
+ ServerApiVersion,
27
+ ValidServerApiVersions,
19
28
  // Errors
20
29
  MongoError: require('./error').MongoError,
21
30
  MongoNetworkError: require('./error').MongoNetworkError,
@@ -204,8 +204,9 @@ function checkServer(monitor, callback) {
204
204
  const maxAwaitTimeMS = monitor.options.heartbeatFrequencyMS;
205
205
  const topologyVersion = monitor[kServer].description.topologyVersion;
206
206
  const isAwaitable = topologyVersion != null;
207
+ const serverApi = monitor[kConnection].serverApi;
207
208
 
208
- const cmd = { ismaster: true };
209
+ const cmd = { [serverApi ? 'hello' : 'ismaster']: true };
209
210
  const options = { socketTimeout: connectTimeoutMS };
210
211
 
211
212
  if (isAwaitable) {
@@ -229,6 +230,11 @@ function checkServer(monitor, callback) {
229
230
  const isMaster = result.result;
230
231
  const rttPinger = monitor[kRTTPinger];
231
232
 
233
+ if ('isWritablePrimary' in isMaster) {
234
+ // Provide pre-hello-style response document.
235
+ isMaster.ismaster = isMaster.isWritablePrimary;
236
+ }
237
+
232
238
  const duration =
233
239
  isAwaitable && rttPinger ? rttPinger.roundTripTime : calculateDurationInMs(start);
234
240
 
@@ -114,6 +114,7 @@ class Server extends EventEmitter {
114
114
  credentials: options.credentials,
115
115
  topology
116
116
  };
117
+ this.serverApi = options.serverApi;
117
118
 
118
119
  // create the connection pool
119
120
  // NOTE: this used to happen in `connect`, we supported overriding pool options there
@@ -251,6 +252,7 @@ class Server extends EventEmitter {
251
252
  if (typeof options === 'function') {
252
253
  (callback = options), (options = {}), (options = options || {});
253
254
  }
255
+ options.serverApi = this.serverApi;
254
256
 
255
257
  if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) {
256
258
  callback(new MongoError('server is closed'));
@@ -200,6 +200,7 @@ class Topology extends EventEmitter {
200
200
  // timer management
201
201
  connectionTimers: new Set()
202
202
  };
203
+ this.serverApi = options.serverApi;
203
204
 
204
205
  if (options.srvHost) {
205
206
  this.s.srvPoller =
@@ -494,7 +495,11 @@ class Topology extends EventEmitter {
494
495
  this.command(
495
496
  'admin.$cmd',
496
497
  { endSessions: sessions },
497
- { readPreference: ReadPreference.primaryPreferred, noResponse: true },
498
+ {
499
+ readPreference: ReadPreference.primaryPreferred,
500
+ noResponse: true,
501
+ serverApi: this.serverApi
502
+ },
498
503
  () => {
499
504
  // intentionally ignored, per spec
500
505
  if (typeof callback === 'function') callback();
@@ -472,17 +472,20 @@ function endTransaction(session, commandName, callback) {
472
472
  if (commandName === 'commitTransaction') {
473
473
  session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
474
474
 
475
- if (
476
- e &&
477
- (e instanceof MongoNetworkError ||
475
+ if (e) {
476
+ if (
477
+ e instanceof MongoNetworkError ||
478
478
  e instanceof MongoWriteConcernError ||
479
479
  isRetryableError(e) ||
480
- isMaxTimeMSExpiredError(e))
481
- ) {
482
- if (isUnknownTransactionCommitResult(e)) {
483
- e.addErrorLabel('UnknownTransactionCommitResult');
480
+ isMaxTimeMSExpiredError(e)
481
+ ) {
482
+ if (isUnknownTransactionCommitResult(e)) {
483
+ e.addErrorLabel('UnknownTransactionCommitResult');
484
484
 
485
- // per txns spec, must unpin session in this case
485
+ // per txns spec, must unpin session in this case
486
+ session.transaction.unpinServer();
487
+ }
488
+ } else if (e.hasErrorLabel('TransientTransactionError')) {
486
489
  session.transaction.unpinServer();
487
490
  }
488
491
  }
@@ -150,7 +150,11 @@ class Transaction {
150
150
  const nextStates = stateMachine[this.state];
151
151
  if (nextStates && nextStates.indexOf(nextState) !== -1) {
152
152
  this.state = nextState;
153
- if (this.state === TxnState.NO_TRANSACTION || this.state === TxnState.STARTING_TRANSACTION) {
153
+ if (
154
+ this.state === TxnState.NO_TRANSACTION ||
155
+ this.state === TxnState.STARTING_TRANSACTION ||
156
+ this.state === TxnState.TRANSACTION_ABORTED
157
+ ) {
154
158
  this.unpinServer();
155
159
  }
156
160
  return;
@@ -432,6 +432,11 @@ function parseQueryString(query, options) {
432
432
  }
433
433
 
434
434
  const normalizedKey = key.toLowerCase();
435
+ if (normalizedKey === 'serverapi') {
436
+ throw new MongoParseError(
437
+ 'URI cannot contain `serverApi`, it can only be passed to the client'
438
+ );
439
+ }
435
440
  const parsedValue = FILE_PATH_OPTIONS.has(normalizedKey)
436
441
  ? value
437
442
  : parseQueryStringItemValue(normalizedKey, value);
@@ -48,6 +48,18 @@ function _command(server, ns, cmd, options, callback) {
48
48
  const serverClusterTime = server.clusterTime;
49
49
  let clusterTime = serverClusterTime;
50
50
  let finalCmd = Object.assign({}, cmd);
51
+
52
+ const serverApi = options.serverApi;
53
+ if (serverApi) {
54
+ finalCmd.apiVersion = serverApi.version || serverApi;
55
+ if (serverApi.strict != null) {
56
+ finalCmd.apiStrict = serverApi.strict;
57
+ }
58
+ if (serverApi.deprecationErrors != null) {
59
+ finalCmd.apiDeprecationErrors = serverApi.deprecationErrors;
60
+ }
61
+ }
62
+
51
63
  if (hasSessionSupport(server) && session) {
52
64
  const sessionClusterTime = session.clusterTime;
53
65
  if (
@@ -60,6 +72,18 @@ function _command(server, ns, cmd, options, callback) {
60
72
  clusterTime = sessionClusterTime;
61
73
  }
62
74
 
75
+ // We need to unpin any read or write commands that happen outside of a pinned
76
+ // transaction, so we check if we have a pinned transaction that is no longer
77
+ // active, and unpin for all except start or commit.
78
+ if (
79
+ !session.transaction.isActive &&
80
+ session.transaction.isPinned &&
81
+ !finalCmd.startTransaction &&
82
+ !finalCmd.commitTransaction
83
+ ) {
84
+ session.transaction.unpinServer();
85
+ }
86
+
63
87
  const err = applySession(session, finalCmd, options);
64
88
  if (err) {
65
89
  return callback(err);
@@ -8,8 +8,13 @@ const maxWireVersion = require('../utils').maxWireVersion;
8
8
  const emitWarning = require('../utils').emitWarning;
9
9
  const command = require('./command');
10
10
 
11
- function killCursors(server, ns, cursorState, callback) {
11
+ function killCursors(server, ns, cursorState, defaultOptions, callback) {
12
+ if (typeof defaultOptions === 'function') {
13
+ callback = defaultOptions;
14
+ defaultOptions = {};
15
+ }
12
16
  callback = typeof callback === 'function' ? callback : () => {};
17
+
13
18
  const cursorId = cursorState.cursorId;
14
19
 
15
20
  if (maxWireVersion(server) < 4) {
@@ -45,7 +50,7 @@ function killCursors(server, ns, cursorState, callback) {
45
50
  cursors: [cursorId]
46
51
  };
47
52
 
48
- const options = {};
53
+ const options = defaultOptions || {};
49
54
  if (typeof cursorState.session === 'object') options.session = cursorState.session;
50
55
 
51
56
  command(server, ns, killCursorCmd, options, (err, result) => {
@@ -37,9 +37,13 @@ function query(server, ns, cmd, cursorState, options, callback) {
37
37
 
38
38
  // If we have explain, we need to rewrite the find command
39
39
  // to wrap it in the explain command
40
- const explain = Explain.fromOptions(options);
41
- if (explain) {
42
- findCmd = decorateWithExplain(findCmd, explain);
40
+ try {
41
+ const explain = Explain.fromOptions(options);
42
+ if (explain) {
43
+ findCmd = decorateWithExplain(findCmd, explain);
44
+ }
45
+ } catch (err) {
46
+ return callback(err);
43
47
  }
44
48
 
45
49
  // NOTE: This actually modifies the passed in cmd, and our code _depends_ on this
@@ -140,8 +144,8 @@ function prepareFindCommand(server, ns, cmd, cursorState) {
140
144
  if (cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS;
141
145
  if (cmd.min) findCmd.min = cmd.min;
142
146
  if (cmd.max) findCmd.max = cmd.max;
143
- findCmd.returnKey = cmd.returnKey ? cmd.returnKey : false;
144
- findCmd.showRecordId = cmd.showDiskLoc ? cmd.showDiskLoc : false;
147
+ if (typeof cmd.returnKey === 'boolean') findCmd.returnKey = cmd.returnKey;
148
+ if (typeof cmd.showDiskLoc === 'boolean') findCmd.showRecordId = cmd.showDiskLoc;
145
149
  if (cmd.snapshot) findCmd.snapshot = cmd.snapshot;
146
150
  if (cmd.tailable) findCmd.tailable = cmd.tailable;
147
151
  if (cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay;
package/lib/cursor.js CHANGED
@@ -165,6 +165,10 @@ class Cursor extends CoreCursor {
165
165
  return this.cmd.sort;
166
166
  }
167
167
 
168
+ set session(clientSession) {
169
+ this.cursorState.session = clientSession;
170
+ }
171
+
168
172
  _initializeCursor(callback) {
169
173
  if (this.operation && this.operation.session != null) {
170
174
  this.cursorState.session = this.operation.session;
package/lib/explain.js CHANGED
@@ -2,16 +2,9 @@
2
2
 
3
3
  const MongoError = require('./core/error').MongoError;
4
4
 
5
- const ExplainVerbosity = {
6
- queryPlanner: 'queryPlanner',
7
- queryPlannerExtended: 'queryPlannerExtended',
8
- executionStats: 'executionStats',
9
- allPlansExecution: 'allPlansExecution'
10
- };
11
-
12
5
  /**
13
6
  * @class
14
- * @property {'queryPlanner'|'queryPlannerExtended'|'executionStats'|'allPlansExecution'} verbosity The verbosity mode for the explain output.
7
+ * @property {string} verbosity The verbosity mode for the explain output, e.g.: 'queryPlanner', 'queryPlannerExtended', 'executionStats', 'allPlansExecution'.
15
8
  */
16
9
  class Explain {
17
10
  /**
@@ -21,7 +14,7 @@ class Explain {
21
14
  * and false as "queryPlanner". Prior to server version 3.6, aggregate()
22
15
  * ignores the verbosity parameter and executes in "queryPlanner".
23
16
  *
24
- * @param {'queryPlanner'|'queryPlannerExtended'|'executionStats'|'allPlansExecution'|boolean} [verbosity] The verbosity mode for the explain output.
17
+ * @param {string|boolean} [verbosity] The verbosity mode for the explain output.
25
18
  */
26
19
  constructor(verbosity) {
27
20
  if (typeof verbosity === 'boolean') {
@@ -35,7 +28,7 @@ class Explain {
35
28
  * Construct an Explain given an options object.
36
29
  *
37
30
  * @param {object} [options] The options object from which to extract the explain.
38
- * @param {'queryPlanner'|'queryPlannerExtended'|'executionStats'|'allPlansExecution'|boolean} [options.explain] The verbosity mode for the explain output
31
+ * @param {string|boolean} [options.explain] The verbosity mode for the explain output.
39
32
  * @return {Explain}
40
33
  */
41
34
  static fromOptions(options) {
@@ -44,11 +37,11 @@ class Explain {
44
37
  }
45
38
 
46
39
  const explain = options.explain;
47
- if (typeof explain === 'boolean' || explain in ExplainVerbosity) {
40
+ if (typeof explain === 'boolean' || typeof explain === 'string') {
48
41
  return new Explain(options.explain);
49
42
  }
50
43
 
51
- throw new MongoError(`explain must be one of ${Object.keys(ExplainVerbosity)} or a boolean`);
44
+ throw new MongoError(`explain must be a string or a boolean`);
52
45
  }
53
46
  }
54
47
 
@@ -7,6 +7,7 @@ var shallowClone = require('../utils').shallowClone;
7
7
  var toError = require('../utils').toError;
8
8
  var util = require('util');
9
9
  var executeLegacyOperation = require('../utils').executeLegacyOperation;
10
+ const deprecateOptions = require('../utils').deprecateOptions;
10
11
 
11
12
  var DEFAULT_GRIDFS_BUCKET_OPTIONS = {
12
13
  bucketName: 'fs',
@@ -79,21 +80,28 @@ util.inherits(GridFSBucket, Emitter);
79
80
  * @param {object} [options.metadata] Optional object to store in the file document's `metadata` field
80
81
  * @param {string} [options.contentType] Optional string to store in the file document's `contentType` field
81
82
  * @param {array} [options.aliases] Optional array of strings to store in the file document's `aliases` field
82
- * @param {boolean} [options.disableMD5=false] If true, disables adding an md5 field to file data
83
+ * @param {boolean} [options.disableMD5=false] **Deprecated** If true, disables adding an md5 field to file data
83
84
  * @return {GridFSBucketWriteStream}
84
85
  */
85
86
 
86
- GridFSBucket.prototype.openUploadStream = function(filename, options) {
87
- if (options) {
88
- options = shallowClone(options);
89
- } else {
90
- options = {};
91
- }
92
- if (!options.chunkSizeBytes) {
93
- options.chunkSizeBytes = this.s.options.chunkSizeBytes;
87
+ GridFSBucket.prototype.openUploadStream = deprecateOptions(
88
+ {
89
+ name: 'GridFSBucket.openUploadStream',
90
+ deprecatedOptions: ['disableMD5'],
91
+ optionsIndex: 1
92
+ },
93
+ function(filename, options) {
94
+ if (options) {
95
+ options = shallowClone(options);
96
+ } else {
97
+ options = {};
98
+ }
99
+ if (!options.chunkSizeBytes) {
100
+ options.chunkSizeBytes = this.s.options.chunkSizeBytes;
101
+ }
102
+ return new GridFSBucketWriteStream(this, filename, options);
94
103
  }
95
- return new GridFSBucketWriteStream(this, filename, options);
96
- };
104
+ );
97
105
 
98
106
  /**
99
107
  * Returns a writable stream (GridFSBucketWriteStream) for writing
@@ -107,25 +115,32 @@ GridFSBucket.prototype.openUploadStream = function(filename, options) {
107
115
  * @param {object} [options.metadata] Optional object to store in the file document's `metadata` field
108
116
  * @param {string} [options.contentType] Optional string to store in the file document's `contentType` field
109
117
  * @param {array} [options.aliases] Optional array of strings to store in the file document's `aliases` field
110
- * @param {boolean} [options.disableMD5=false] If true, disables adding an md5 field to file data
118
+ * @param {boolean} [options.disableMD5=false] **Deprecated** If true, disables adding an md5 field to file data
111
119
  * @return {GridFSBucketWriteStream}
112
120
  */
113
121
 
114
- GridFSBucket.prototype.openUploadStreamWithId = function(id, filename, options) {
115
- if (options) {
116
- options = shallowClone(options);
117
- } else {
118
- options = {};
119
- }
122
+ GridFSBucket.prototype.openUploadStreamWithId = deprecateOptions(
123
+ {
124
+ name: 'GridFSBucket.openUploadStreamWithId',
125
+ deprecatedOptions: ['disableMD5'],
126
+ optionsIndex: 2
127
+ },
128
+ function(id, filename, options) {
129
+ if (options) {
130
+ options = shallowClone(options);
131
+ } else {
132
+ options = {};
133
+ }
120
134
 
121
- if (!options.chunkSizeBytes) {
122
- options.chunkSizeBytes = this.s.options.chunkSizeBytes;
123
- }
135
+ if (!options.chunkSizeBytes) {
136
+ options.chunkSizeBytes = this.s.options.chunkSizeBytes;
137
+ }
124
138
 
125
- options.id = id;
139
+ options.id = id;
126
140
 
127
- return new GridFSBucketWriteStream(this, filename, options);
128
- };
141
+ return new GridFSBucketWriteStream(this, filename, options);
142
+ }
143
+ );
129
144
 
130
145
  /**
131
146
  * Returns a readable stream (GridFSBucketReadStream) for streaming file
@@ -5,10 +5,9 @@ var crypto = require('crypto');
5
5
  var stream = require('stream');
6
6
  var util = require('util');
7
7
  var Buffer = require('safe-buffer').Buffer;
8
+ const deprecateOptions = require('../utils').deprecateOptions;
8
9
 
9
- var ERROR_NAMESPACE_NOT_FOUND = 26;
10
-
11
- module.exports = GridFSBucketWriteStream;
10
+ const ERROR_NAMESPACE_NOT_FOUND = 26;
12
11
 
13
12
  /**
14
13
  * A writable stream that enables you to write buffers to GridFS.
@@ -31,42 +30,49 @@ module.exports = GridFSBucketWriteStream;
31
30
  * @fires GridFSBucketWriteStream#finish
32
31
  */
33
32
 
34
- function GridFSBucketWriteStream(bucket, filename, options) {
35
- options = options || {};
36
- stream.Writable.call(this, options);
37
- this.bucket = bucket;
38
- this.chunks = bucket.s._chunksCollection;
39
- this.filename = filename;
40
- this.files = bucket.s._filesCollection;
41
- this.options = options;
42
- // Signals the write is all done
43
- this.done = false;
44
-
45
- this.id = options.id ? options.id : core.BSON.ObjectId();
46
- this.chunkSizeBytes = this.options.chunkSizeBytes;
47
- this.bufToStore = Buffer.alloc(this.chunkSizeBytes);
48
- this.length = 0;
49
- this.md5 = !options.disableMD5 && crypto.createHash('md5');
50
- this.n = 0;
51
- this.pos = 0;
52
- this.state = {
53
- streamEnd: false,
54
- outstandingRequests: 0,
55
- errored: false,
56
- aborted: false,
57
- promiseLibrary: this.bucket.s.promiseLibrary
58
- };
59
-
60
- if (!this.bucket.s.calledOpenUploadStream) {
61
- this.bucket.s.calledOpenUploadStream = true;
62
-
63
- var _this = this;
64
- checkIndexes(this, function() {
65
- _this.bucket.s.checkedIndexes = true;
66
- _this.bucket.emit('index');
67
- });
33
+ const GridFSBucketWriteStream = deprecateOptions(
34
+ {
35
+ name: 'GridFSBucketWriteStream',
36
+ deprecatedOptions: ['disableMD5'],
37
+ optionsIndex: 2
38
+ },
39
+ function(bucket, filename, options) {
40
+ options = options || {};
41
+ stream.Writable.call(this, options);
42
+ this.bucket = bucket;
43
+ this.chunks = bucket.s._chunksCollection;
44
+ this.filename = filename;
45
+ this.files = bucket.s._filesCollection;
46
+ this.options = options;
47
+ // Signals the write is all done
48
+ this.done = false;
49
+
50
+ this.id = options.id ? options.id : core.BSON.ObjectId();
51
+ this.chunkSizeBytes = this.options.chunkSizeBytes;
52
+ this.bufToStore = Buffer.alloc(this.chunkSizeBytes);
53
+ this.length = 0;
54
+ this.md5 = !options.disableMD5 && crypto.createHash('md5');
55
+ this.n = 0;
56
+ this.pos = 0;
57
+ this.state = {
58
+ streamEnd: false,
59
+ outstandingRequests: 0,
60
+ errored: false,
61
+ aborted: false,
62
+ promiseLibrary: this.bucket.s.promiseLibrary
63
+ };
64
+
65
+ if (!this.bucket.s.calledOpenUploadStream) {
66
+ this.bucket.s.calledOpenUploadStream = true;
67
+
68
+ var _this = this;
69
+ checkIndexes(this, function() {
70
+ _this.bucket.s.checkedIndexes = true;
71
+ _this.bucket.emit('index');
72
+ });
73
+ }
68
74
  }
69
- }
75
+ );
70
76
 
71
77
  util.inherits(GridFSBucketWriteStream, stream.Writable);
72
78
 
@@ -539,3 +545,5 @@ function checkAborted(_this, callback) {
539
545
  }
540
546
  return false;
541
547
  }
548
+
549
+ module.exports = GridFSBucketWriteStream;
@@ -5,6 +5,7 @@ const Db = require('./db');
5
5
  const EventEmitter = require('events').EventEmitter;
6
6
  const inherits = require('util').inherits;
7
7
  const MongoError = require('./core').MongoError;
8
+ const ValidServerApiVersions = require('./core').ValidServerApiVersions;
8
9
  const deprecate = require('util').deprecate;
9
10
  const WriteConcern = require('./write_concern');
10
11
  const MongoDBNamespace = require('./utils').MongoDBNamespace;
@@ -156,6 +157,7 @@ const validOptions = require('./operations/connect').validOptions;
156
157
  * @property {number} [numberOfRetries] (**default**: 5) The number of retries for a tailable cursor
157
158
  * @property {boolean} [auto_reconnect] (**default**: true) Enable auto reconnecting for single server instances
158
159
  * @property {boolean} [monitorCommands] (**default**: false) Enable command monitoring for this client
160
+ * @property {string|ServerApi} [serverApi] (**default**: undefined) The server API version
159
161
  * @property {number} [minSize] If present, the connection pool will be initialized with minSize connections, and will never dip below minSize connections
160
162
  * @property {boolean} [useNewUrlParser] (**default**: true) Determines whether or not to use the new url parser. Enables the new, spec-compliant, url parser shipped in the core driver. This url parser fixes a number of problems with the original parser, and aims to outright replace that parser in the near future. Defaults to true, and must be explicitly set to false to use the legacy url parser.
161
163
  * @property {boolean} [useUnifiedTopology] Enables the new unified topology layer
@@ -191,16 +193,38 @@ const validOptions = require('./operations/connect').validOptions;
191
193
  * @param {MongoClientOptions} [options] Optional settings
192
194
  */
193
195
  function MongoClient(url, options) {
196
+ options = options || {};
194
197
  if (!(this instanceof MongoClient)) return new MongoClient(url, options);
195
198
  // Set up event emitter
196
199
  EventEmitter.call(this);
197
200
 
198
- if (options && options.autoEncryption) require('./encrypter'); // Does CSFLE lib check
201
+ if (options.autoEncryption) require('./encrypter'); // Does CSFLE lib check
202
+
203
+ if (options.serverApi) {
204
+ const serverApiToValidate =
205
+ typeof options.serverApi === 'string' ? { version: options.serverApi } : options.serverApi;
206
+ const versionToValidate = serverApiToValidate && serverApiToValidate.version;
207
+ if (!versionToValidate) {
208
+ throw new MongoError(
209
+ `Invalid \`serverApi\` property; must specify a version from the following enum: ["${ValidServerApiVersions.join(
210
+ '", "'
211
+ )}"]`
212
+ );
213
+ }
214
+ if (!ValidServerApiVersions.some(v => v === versionToValidate)) {
215
+ throw new MongoError(
216
+ `Invalid server API version=${versionToValidate}; must be in the following enum: ["${ValidServerApiVersions.join(
217
+ '", "'
218
+ )}"]`
219
+ );
220
+ }
221
+ options.serverApi = serverApiToValidate;
222
+ }
199
223
 
200
224
  // The internal state
201
225
  this.s = {
202
- url: url,
203
- options: options || {},
226
+ url,
227
+ options,
204
228
  promiseLibrary: (options && options.promiseLibrary) || Promise,
205
229
  dbCache: new Map(),
206
230
  sessions: new Set(),
@@ -364,17 +388,18 @@ MongoClient.prototype.db = function(dbName, options) {
364
388
  * Check if MongoClient is connected
365
389
  *
366
390
  * @method
391
+ * @deprecated
367
392
  * @param {object} [options] Optional settings.
368
393
  * @param {boolean} [options.noListener=false] Do not make the db an event listener to the original connection.
369
394
  * @param {boolean} [options.returnNonCachedInstance=false] Control if you want to return a cached instance or have a new one created
370
395
  * @return {boolean}
371
396
  */
372
- MongoClient.prototype.isConnected = function(options) {
397
+ MongoClient.prototype.isConnected = deprecate(function(options) {
373
398
  options = options || {};
374
399
 
375
400
  if (!this.topology) return false;
376
401
  return this.topology.isConnected(options);
377
- };
402
+ }, 'isConnected is deprecated and will be removed in the next major version');
378
403
 
379
404
  /**
380
405
  * Connect to MongoDB using a url as documented at
@@ -138,6 +138,7 @@ const validOptionNames = [
138
138
  'auto_reconnect',
139
139
  'minSize',
140
140
  'monitorCommands',
141
+ 'serverApi',
141
142
  'retryWrites',
142
143
  'retryReads',
143
144
  'useNewUrlParser',
@@ -3,41 +3,69 @@
3
3
  const Aspect = require('./operation').Aspect;
4
4
  const defineAspects = require('./operation').defineAspects;
5
5
  const CommandOperationV2 = require('./command_v2');
6
+ const maxWireVersion = require('../core/utils').maxWireVersion;
7
+ const CountDocumentsOperation = require('./count_documents');
6
8
 
7
9
  class EstimatedDocumentCountOperation extends CommandOperationV2 {
8
- constructor(collection, query, options) {
9
- if (typeof options === 'undefined') {
10
- options = query;
11
- query = undefined;
12
- }
13
-
10
+ constructor(collection, options) {
14
11
  super(collection, options);
12
+ this.collection = collection;
15
13
  this.collectionName = collection.s.namespace.collection;
16
- if (query) {
17
- this.query = query;
18
- }
19
14
  }
20
15
 
21
16
  execute(server, callback) {
22
- const options = this.options;
23
- const cmd = { count: this.collectionName };
17
+ if (maxWireVersion(server) < 12) {
18
+ return this.executeLegacy(server, callback);
19
+ }
20
+ // if the user specifies a filter, use a CountDocumentsOperation instead
21
+ if (this.options.query) {
22
+ const op = new CountDocumentsOperation(this.collection, this.options.query, this.options);
23
+ return op.execute(server, callback);
24
+ }
25
+ const pipeline = [{ $collStats: { count: {} } }, { $group: { _id: 1, n: { $sum: '$count' } } }];
26
+ const cmd = { aggregate: this.collectionName, pipeline, cursor: {} };
24
27
 
25
- if (this.query) {
26
- cmd.query = this.query;
28
+ if (typeof this.options.maxTimeMS === 'number') {
29
+ cmd.maxTimeMS = this.options.maxTimeMS;
27
30
  }
28
31
 
32
+ super.executeCommand(server, cmd, (err, response) => {
33
+ if (err && err.code !== 26) {
34
+ callback(err);
35
+ return;
36
+ }
37
+
38
+ callback(
39
+ undefined,
40
+ (response &&
41
+ response.cursor &&
42
+ response.cursor.firstBatch &&
43
+ response.cursor.firstBatch[0].n) ||
44
+ 0
45
+ );
46
+ });
47
+ }
48
+
49
+ executeLegacy(server, callback) {
50
+ const cmd = { count: this.collectionName };
51
+
52
+ const options = this.options;
53
+ if (options.query) {
54
+ cmd.query = options.query;
55
+ }
56
+ if (options.hint) {
57
+ cmd.hint = options.hint;
58
+ }
59
+ if (typeof options.maxTimeMS === 'number') {
60
+ cmd.maxTimeMS = options.maxTimeMS;
61
+ }
29
62
  if (typeof options.skip === 'number') {
30
63
  cmd.skip = options.skip;
31
64
  }
32
-
33
65
  if (typeof options.limit === 'number') {
34
66
  cmd.limit = options.limit;
35
67
  }
36
68
 
37
- if (options.hint) {
38
- cmd.hint = options.hint;
39
- }
40
-
41
69
  super.executeCommand(server, cmd, (err, response) => {
42
70
  if (err) {
43
71
  callback(err);
@@ -44,6 +44,8 @@ class NativeTopology extends Topology {
44
44
  // Translate all the options to the core types
45
45
  clonedOptions = translateOptions(clonedOptions, socketOptions);
46
46
 
47
+ clonedOptions.serverApi = options.serverApi;
48
+
47
49
  super(servers, clonedOptions);
48
50
  }
49
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongodb",
3
- "version": "3.6.12",
3
+ "version": "3.7.0",
4
4
  "description": "The official MongoDB driver for Node.js",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -102,7 +102,7 @@
102
102
  "check:tls": "mocha --opts '{}' test/manual/tls_support.test.js",
103
103
  "format": "npm run check:lint -- --fix",
104
104
  "release": "standard-version -i HISTORY.md",
105
- "test": "npm run lint && mocha --recursive test/functional test/unit"
105
+ "test": "npm run check:lint && mocha --recursive test/functional test/unit"
106
106
  },
107
107
  "homepage": "https://github.com/mongodb/node-mongodb-native",
108
108
  "optionalDependencies": {