1 | ;
|
2 |
|
3 | /*!
|
4 | * Module dependencies.
|
5 | */
|
6 |
|
7 | const ChangeStream = require('./cursor/ChangeStream');
|
8 | const EventEmitter = require('events').EventEmitter;
|
9 | const Schema = require('./schema');
|
10 | const Collection = require('./driver').get().Collection;
|
11 | const STATES = require('./connectionstate');
|
12 | const MongooseError = require('./error/index');
|
13 | const PromiseProvider = require('./promise_provider');
|
14 | const ServerSelectionError = require('./error/serverSelection');
|
15 | const applyPlugins = require('./helpers/schema/applyPlugins');
|
16 | const promiseOrCallback = require('./helpers/promiseOrCallback');
|
17 | const get = require('./helpers/get');
|
18 | const immediate = require('./helpers/immediate');
|
19 | const mongodb = require('mongodb');
|
20 | const pkg = require('../package.json');
|
21 | const utils = require('./utils');
|
22 |
|
23 | const parseConnectionString = require('mongodb/lib/core').parseConnectionString;
|
24 |
|
25 | let id = 0;
|
26 |
|
27 | /*!
|
28 | * A list of authentication mechanisms that don't require a password for authentication.
|
29 | * This is used by the authMechanismDoesNotRequirePassword method.
|
30 | *
|
31 | * @api private
|
32 | */
|
33 | const noPasswordAuthMechanisms = [
|
34 | 'MONGODB-X509'
|
35 | ];
|
36 |
|
37 | /**
|
38 | * Connection constructor
|
39 | *
|
40 | * For practical reasons, a Connection equals a Db.
|
41 | *
|
42 | * @param {Mongoose} base a mongoose instance
|
43 | * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
|
44 | * @event `connecting`: Emitted when `connection.openUri()` is executed on this connection.
|
45 | * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios.
|
46 | * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models.
|
47 | * @event `disconnecting`: Emitted when `connection.close()` was executed.
|
48 | * @event `disconnected`: Emitted after getting disconnected from the db.
|
49 | * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models.
|
50 | * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successful connection.
|
51 | * @event `error`: Emitted when an error occurs on this connection.
|
52 | * @event `fullsetup`: Emitted after the driver has connected to primary and all secondaries if specified in the connection string.
|
53 | * @api public
|
54 | */
|
55 |
|
56 | function Connection(base) {
|
57 | this.base = base;
|
58 | this.collections = {};
|
59 | this.models = {};
|
60 | this.config = { autoIndex: true };
|
61 | this.replica = false;
|
62 | this.options = null;
|
63 | this.otherDbs = []; // FIXME: To be replaced with relatedDbs
|
64 | this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection
|
65 | this.states = STATES;
|
66 | this._readyState = STATES.disconnected;
|
67 | this._closeCalled = false;
|
68 | this._hasOpened = false;
|
69 | this.plugins = [];
|
70 | this.id = id++;
|
71 | }
|
72 |
|
73 | /*!
|
74 | * Inherit from EventEmitter
|
75 | */
|
76 |
|
77 | Connection.prototype.__proto__ = EventEmitter.prototype;
|
78 |
|
79 | /**
|
80 | * Connection ready state
|
81 | *
|
82 | * - 0 = disconnected
|
83 | * - 1 = connected
|
84 | * - 2 = connecting
|
85 | * - 3 = disconnecting
|
86 | *
|
87 | * Each state change emits its associated event name.
|
88 | *
|
89 | * ####Example
|
90 | *
|
91 | * conn.on('connected', callback);
|
92 | * conn.on('disconnected', callback);
|
93 | *
|
94 | * @property readyState
|
95 | * @memberOf Connection
|
96 | * @instance
|
97 | * @api public
|
98 | */
|
99 |
|
100 | Object.defineProperty(Connection.prototype, 'readyState', {
|
101 | get: function() {
|
102 | return this._readyState;
|
103 | },
|
104 | set: function(val) {
|
105 | if (!(val in STATES)) {
|
106 | throw new Error('Invalid connection state: ' + val);
|
107 | }
|
108 |
|
109 | if (this._readyState !== val) {
|
110 | this._readyState = val;
|
111 | // [legacy] loop over the otherDbs on this connection and change their state
|
112 | for (let i = 0; i < this.otherDbs.length; i++) {
|
113 | this.otherDbs[i].readyState = val;
|
114 | }
|
115 |
|
116 | // loop over relatedDbs on this connection and change their state
|
117 | for (const k in this.relatedDbs) {
|
118 | this.relatedDbs[k].readyState = val;
|
119 | }
|
120 |
|
121 | if (STATES.connected === val) {
|
122 | this._hasOpened = true;
|
123 | }
|
124 |
|
125 | this.emit(STATES[val]);
|
126 | }
|
127 | }
|
128 | });
|
129 |
|
130 | /**
|
131 | * Gets the value of the option `key`. Equivalent to `conn.options[key]`
|
132 | *
|
133 | * ####Example:
|
134 | *
|
135 | * conn.get('test'); // returns the 'test' value
|
136 | *
|
137 | * @param {String} key
|
138 | * @method get
|
139 | * @api public
|
140 | */
|
141 |
|
142 | Connection.prototype.get = function(key) {
|
143 | return get(this.options, key);
|
144 | };
|
145 |
|
146 | /**
|
147 | * Sets the value of the option `key`. Equivalent to `conn.options[key] = val`
|
148 | *
|
149 | * Supported options include:
|
150 | *
|
151 | * - `maxTimeMS`: Set [`maxTimeMS`](/docs/api.html#query_Query-maxTimeMS) for all queries on this connection.
|
152 | * - `useFindAndModify`: Set to `false` to work around the [`findAndModify()` deprecation warning](/docs/deprecations.html#findandmodify)
|
153 | *
|
154 | * ####Example:
|
155 | *
|
156 | * conn.set('test', 'foo');
|
157 | * conn.get('test'); // 'foo'
|
158 | * conn.options.test; // 'foo'
|
159 | *
|
160 | * @param {String} key
|
161 | * @param {Any} val
|
162 | * @method set
|
163 | * @api public
|
164 | */
|
165 |
|
166 | Connection.prototype.set = function(key, val) {
|
167 | this.options = this.options || {};
|
168 | this.options[key] = val;
|
169 | return val;
|
170 | };
|
171 |
|
172 | /**
|
173 | * A hash of the collections associated with this connection
|
174 | *
|
175 | * @property collections
|
176 | * @memberOf Connection
|
177 | * @instance
|
178 | * @api public
|
179 | */
|
180 |
|
181 | Connection.prototype.collections;
|
182 |
|
183 | /**
|
184 | * The name of the database this connection points to.
|
185 | *
|
186 | * ####Example
|
187 | *
|
188 | * mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb"
|
189 | *
|
190 | * @property name
|
191 | * @memberOf Connection
|
192 | * @instance
|
193 | * @api public
|
194 | */
|
195 |
|
196 | Connection.prototype.name;
|
197 |
|
198 | /**
|
199 | * A [POJO](https://masteringjs.io/tutorials/fundamentals/pojo) containing
|
200 | * a map from model names to models. Contains all models that have been
|
201 | * added to this connection using [`Connection#model()`](/docs/api/connection.html#connection_Connection-model).
|
202 | *
|
203 | * ####Example
|
204 | *
|
205 | * const conn = mongoose.createConnection();
|
206 | * const Test = conn.model('Test', mongoose.Schema({ name: String }));
|
207 | *
|
208 | * Object.keys(conn.models).length; // 1
|
209 | * conn.models.Test === Test; // true
|
210 | *
|
211 | * @property models
|
212 | * @memberOf Connection
|
213 | * @instance
|
214 | * @api public
|
215 | */
|
216 |
|
217 | Connection.prototype.models;
|
218 |
|
219 | /**
|
220 | * A number identifier for this connection. Used for debugging when
|
221 | * you have [multiple connections](/docs/connections.html#multiple_connections).
|
222 | *
|
223 | * ####Example
|
224 | *
|
225 | * // The default connection has `id = 0`
|
226 | * mongoose.connection.id; // 0
|
227 | *
|
228 | * // If you create a new connection, Mongoose increments id
|
229 | * const conn = mongoose.createConnection();
|
230 | * conn.id; // 1
|
231 | *
|
232 | * @property id
|
233 | * @memberOf Connection
|
234 | * @instance
|
235 | * @api public
|
236 | */
|
237 |
|
238 | Connection.prototype.id;
|
239 |
|
240 | /**
|
241 | * The plugins that will be applied to all models created on this connection.
|
242 | *
|
243 | * ####Example:
|
244 | *
|
245 | * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
|
246 | * db.plugin(() => console.log('Applied'));
|
247 | * db.plugins.length; // 1
|
248 | *
|
249 | * db.model('Test', new Schema({})); // Prints "Applied"
|
250 | *
|
251 | * @property plugins
|
252 | * @memberOf Connection
|
253 | * @instance
|
254 | * @api public
|
255 | */
|
256 |
|
257 | Object.defineProperty(Connection.prototype, 'plugins', {
|
258 | configurable: false,
|
259 | enumerable: true,
|
260 | writable: true
|
261 | });
|
262 |
|
263 | /**
|
264 | * The host name portion of the URI. If multiple hosts, such as a replica set,
|
265 | * this will contain the first host name in the URI
|
266 | *
|
267 | * ####Example
|
268 | *
|
269 | * mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost"
|
270 | *
|
271 | * @property host
|
272 | * @memberOf Connection
|
273 | * @instance
|
274 | * @api public
|
275 | */
|
276 |
|
277 | Object.defineProperty(Connection.prototype, 'host', {
|
278 | configurable: true,
|
279 | enumerable: true,
|
280 | writable: true
|
281 | });
|
282 |
|
283 | /**
|
284 | * The port portion of the URI. If multiple hosts, such as a replica set,
|
285 | * this will contain the port from the first host name in the URI.
|
286 | *
|
287 | * ####Example
|
288 | *
|
289 | * mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017
|
290 | *
|
291 | * @property port
|
292 | * @memberOf Connection
|
293 | * @instance
|
294 | * @api public
|
295 | */
|
296 |
|
297 | Object.defineProperty(Connection.prototype, 'port', {
|
298 | configurable: true,
|
299 | enumerable: true,
|
300 | writable: true
|
301 | });
|
302 |
|
303 | /**
|
304 | * The username specified in the URI
|
305 | *
|
306 | * ####Example
|
307 | *
|
308 | * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val"
|
309 | *
|
310 | * @property user
|
311 | * @memberOf Connection
|
312 | * @instance
|
313 | * @api public
|
314 | */
|
315 |
|
316 | Object.defineProperty(Connection.prototype, 'user', {
|
317 | configurable: true,
|
318 | enumerable: true,
|
319 | writable: true
|
320 | });
|
321 |
|
322 | /**
|
323 | * The password specified in the URI
|
324 | *
|
325 | * ####Example
|
326 | *
|
327 | * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw"
|
328 | *
|
329 | * @property pass
|
330 | * @memberOf Connection
|
331 | * @instance
|
332 | * @api public
|
333 | */
|
334 |
|
335 | Object.defineProperty(Connection.prototype, 'pass', {
|
336 | configurable: true,
|
337 | enumerable: true,
|
338 | writable: true
|
339 | });
|
340 |
|
341 | /**
|
342 | * The mongodb.Db instance, set when the connection is opened
|
343 | *
|
344 | * @property db
|
345 | * @memberOf Connection
|
346 | * @instance
|
347 | * @api public
|
348 | */
|
349 |
|
350 | Connection.prototype.db;
|
351 |
|
352 | /**
|
353 | * A hash of the global options that are associated with this connection
|
354 | *
|
355 | * @property config
|
356 | * @memberOf Connection
|
357 | * @instance
|
358 | * @api public
|
359 | */
|
360 |
|
361 | Connection.prototype.config;
|
362 |
|
363 | /**
|
364 | * Helper for `createCollection()`. Will explicitly create the given collection
|
365 | * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/)
|
366 | * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose.
|
367 | *
|
368 | * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
|
369 | *
|
370 | * @method createCollection
|
371 | * @param {string} collection The collection to create
|
372 | * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
|
373 | * @param {Function} [callback]
|
374 | * @return {Promise}
|
375 | * @api public
|
376 | */
|
377 |
|
378 | Connection.prototype.createCollection = _wrapConnHelper(function createCollection(collection, options, cb) {
|
379 | if (typeof options === 'function') {
|
380 | cb = options;
|
381 | options = {};
|
382 | }
|
383 | this.db.createCollection(collection, options, cb);
|
384 | });
|
385 |
|
386 | /**
|
387 | * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
|
388 | * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/),
|
389 | * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
|
390 | *
|
391 | * ####Example:
|
392 | *
|
393 | * const session = await conn.startSession();
|
394 | * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
|
395 | * await doc.remove();
|
396 | * // `doc` will always be null, even if reading from a replica set
|
397 | * // secondary. Without causal consistency, it is possible to
|
398 | * // get a doc back from the below query if the query reads from a
|
399 | * // secondary that is experiencing replication lag.
|
400 | * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
|
401 | *
|
402 | *
|
403 | * @method startSession
|
404 | * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
|
405 | * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
|
406 | * @param {Function} [callback]
|
407 | * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
|
408 | * @api public
|
409 | */
|
410 |
|
411 | Connection.prototype.startSession = _wrapConnHelper(function startSession(options, cb) {
|
412 | if (typeof options === 'function') {
|
413 | cb = options;
|
414 | options = null;
|
415 | }
|
416 | const session = this.client.startSession(options);
|
417 | cb(null, session);
|
418 | });
|
419 |
|
420 | /**
|
421 | * Helper for `dropCollection()`. Will delete the given collection, including
|
422 | * all documents and indexes.
|
423 | *
|
424 | * @method dropCollection
|
425 | * @param {string} collection The collection to delete
|
426 | * @param {Function} [callback]
|
427 | * @return {Promise}
|
428 | * @api public
|
429 | */
|
430 |
|
431 | Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(collection, cb) {
|
432 | this.db.dropCollection(collection, cb);
|
433 | });
|
434 |
|
435 | /**
|
436 | * Helper for `dropDatabase()`. Deletes the given database, including all
|
437 | * collections, documents, and indexes.
|
438 | *
|
439 | * ####Example:
|
440 | *
|
441 | * const conn = mongoose.createConnection('mongodb://localhost:27017/mydb');
|
442 | * // Deletes the entire 'mydb' database
|
443 | * await conn.dropDatabase();
|
444 | *
|
445 | * @method dropDatabase
|
446 | * @param {Function} [callback]
|
447 | * @return {Promise}
|
448 | * @api public
|
449 | */
|
450 |
|
451 | Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) {
|
452 | // If `dropDatabase()` is called, this model's collection will not be
|
453 | // init-ed. It is sufficiently common to call `dropDatabase()` after
|
454 | // `mongoose.connect()` but before creating models that we want to
|
455 | // support this. See gh-6967
|
456 | for (const name of Object.keys(this.models)) {
|
457 | delete this.models[name].$init;
|
458 | }
|
459 | this.db.dropDatabase(cb);
|
460 | });
|
461 |
|
462 | /*!
|
463 | * ignore
|
464 | */
|
465 |
|
466 | function _wrapConnHelper(fn) {
|
467 | return function() {
|
468 | const cb = arguments.length > 0 ? arguments[arguments.length - 1] : null;
|
469 | const argsWithoutCb = typeof cb === 'function' ?
|
470 | Array.prototype.slice.call(arguments, 0, arguments.length - 1) :
|
471 | Array.prototype.slice.call(arguments);
|
472 | const disconnectedError = new MongooseError('Connection ' + this.id +
|
473 | ' was disconnected when calling `' + fn.name + '`');
|
474 | return promiseOrCallback(cb, cb => {
|
475 | // Make it ok to call collection helpers before `mongoose.connect()`
|
476 | // as long as `mongoose.connect()` is called on the same tick.
|
477 | // Re: gh-8534
|
478 | immediate(() => {
|
479 | if (this.readyState === STATES.connecting) {
|
480 | this.once('open', function() {
|
481 | fn.apply(this, argsWithoutCb.concat([cb]));
|
482 | });
|
483 | } else if (this.readyState === STATES.disconnected && this.db == null) {
|
484 | cb(disconnectedError);
|
485 | } else {
|
486 | fn.apply(this, argsWithoutCb.concat([cb]));
|
487 | }
|
488 | });
|
489 | });
|
490 | };
|
491 | }
|
492 |
|
493 | /**
|
494 | * error
|
495 | *
|
496 | * Graceful error handling, passes error to callback
|
497 | * if available, else emits error on the connection.
|
498 | *
|
499 | * @param {Error} err
|
500 | * @param {Function} callback optional
|
501 | * @api private
|
502 | */
|
503 |
|
504 | Connection.prototype.error = function(err, callback) {
|
505 | if (callback) {
|
506 | callback(err);
|
507 | return null;
|
508 | }
|
509 | if (this.listeners('error').length > 0) {
|
510 | this.emit('error', err);
|
511 | }
|
512 | return Promise.reject(err);
|
513 | };
|
514 |
|
515 | /**
|
516 | * Called when the connection is opened
|
517 | *
|
518 | * @api private
|
519 | */
|
520 |
|
521 | Connection.prototype.onOpen = function() {
|
522 | this.readyState = STATES.connected;
|
523 |
|
524 | // avoid having the collection subscribe to our event emitter
|
525 | // to prevent 0.3 warning
|
526 | for (const i in this.collections) {
|
527 | if (utils.object.hasOwnProperty(this.collections, i)) {
|
528 | this.collections[i].onOpen();
|
529 | }
|
530 | }
|
531 |
|
532 | this.emit('open');
|
533 | };
|
534 |
|
535 | /**
|
536 | * Opens the connection with a URI using `MongoClient.connect()`.
|
537 | *
|
538 | * @param {String} uri The URI to connect with.
|
539 | * @param {Object} [options] Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect
|
540 | * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection.
|
541 | * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string.
|
542 | * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility.
|
543 | * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
|
544 | * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
|
545 | * @param {Boolean} [options.useNewUrlParser=false] False by default. Set to `true` to opt in to the MongoDB driver's new URL parser logic.
|
546 | * @param {Boolean} [options.useUnifiedTopology=false] False by default. Set to `true` to opt in to the MongoDB driver's replica set and sharded cluster monitoring engine.
|
547 | * @param {Boolean} [options.useCreateIndex=true] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init).
|
548 | * @param {Boolean} [options.useFindAndModify=true] True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`.
|
549 | * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections.
|
550 | * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above.
|
551 | * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html).
|
552 | * @param {Number} [options.poolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
|
553 | * @param {Number} [options.bufferMaxEntries] This option does nothing if `useUnifiedTopology` is set. The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection.
|
554 | * @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback).
|
555 | * @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes.
|
556 | * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both.
|
557 | * @param {Function} [callback]
|
558 | * @returns {Connection} this
|
559 | * @api public
|
560 | */
|
561 |
|
562 | Connection.prototype.openUri = function(uri, options, callback) {
|
563 | this.readyState = STATES.connecting;
|
564 | this._closeCalled = false;
|
565 |
|
566 | if (typeof options === 'function') {
|
567 | callback = options;
|
568 | options = null;
|
569 | }
|
570 |
|
571 | if (['string', 'number'].indexOf(typeof options) !== -1) {
|
572 | throw new MongooseError('Mongoose 5.x no longer supports ' +
|
573 | '`mongoose.connect(host, dbname, port)` or ' +
|
574 | '`mongoose.createConnection(host, dbname, port)`. See ' +
|
575 | 'http://mongoosejs.com/docs/connections.html for supported connection syntax');
|
576 | }
|
577 |
|
578 | if (typeof uri !== 'string') {
|
579 | throw new MongooseError('The `uri` parameter to `openUri()` must be a ' +
|
580 | `string, got "${typeof uri}". Make sure the first parameter to ` +
|
581 | '`mongoose.connect()` or `mongoose.createConnection()` is a string.');
|
582 | }
|
583 |
|
584 | if (callback != null && typeof callback !== 'function') {
|
585 | throw new MongooseError('3rd parameter to `mongoose.connect()` or ' +
|
586 | '`mongoose.createConnection()` must be a function, got "' +
|
587 | typeof callback + '"');
|
588 | }
|
589 |
|
590 | const Promise = PromiseProvider.get();
|
591 | const _this = this;
|
592 |
|
593 | if (options) {
|
594 | options = utils.clone(options);
|
595 | const autoIndex = options.config && options.config.autoIndex != null ?
|
596 | options.config.autoIndex :
|
597 | options.autoIndex;
|
598 | if (autoIndex != null) {
|
599 | this.config.autoIndex = autoIndex !== false;
|
600 | delete options.config;
|
601 | delete options.autoIndex;
|
602 | }
|
603 |
|
604 | if ('autoCreate' in options) {
|
605 | this.config.autoCreate = !!options.autoCreate;
|
606 | delete options.autoCreate;
|
607 | }
|
608 | if ('useCreateIndex' in options) {
|
609 | this.config.useCreateIndex = !!options.useCreateIndex;
|
610 | delete options.useCreateIndex;
|
611 | }
|
612 |
|
613 | if ('useFindAndModify' in options) {
|
614 | this.config.useFindAndModify = !!options.useFindAndModify;
|
615 | delete options.useFindAndModify;
|
616 | }
|
617 |
|
618 | // Backwards compat
|
619 | if (options.user || options.pass) {
|
620 | options.auth = options.auth || {};
|
621 | options.auth.user = options.user;
|
622 | options.auth.password = options.pass;
|
623 |
|
624 | this.user = options.user;
|
625 | this.pass = options.pass;
|
626 | }
|
627 | delete options.user;
|
628 | delete options.pass;
|
629 |
|
630 | if (options.bufferCommands != null) {
|
631 | options.bufferMaxEntries = 0;
|
632 | this.config.bufferCommands = options.bufferCommands;
|
633 | delete options.bufferCommands;
|
634 | }
|
635 |
|
636 | if (options.useMongoClient != null) {
|
637 | handleUseMongoClient(options);
|
638 | }
|
639 | } else {
|
640 | options = {};
|
641 | }
|
642 |
|
643 | this._connectionOptions = options;
|
644 | const dbName = options.dbName;
|
645 | if (dbName != null) {
|
646 | this.$dbName = dbName;
|
647 | }
|
648 | delete options.dbName;
|
649 |
|
650 | if (!('promiseLibrary' in options)) {
|
651 | options.promiseLibrary = PromiseProvider.get();
|
652 | }
|
653 | if (!('useNewUrlParser' in options)) {
|
654 | if ('useNewUrlParser' in this.base.options) {
|
655 | options.useNewUrlParser = this.base.options.useNewUrlParser;
|
656 | } else {
|
657 | options.useNewUrlParser = false;
|
658 | }
|
659 | }
|
660 | if (!utils.hasUserDefinedProperty(options, 'useUnifiedTopology')) {
|
661 | if (utils.hasUserDefinedProperty(this.base.options, 'useUnifiedTopology')) {
|
662 | options.useUnifiedTopology = this.base.options.useUnifiedTopology;
|
663 | } else {
|
664 | options.useUnifiedTopology = false;
|
665 | }
|
666 | }
|
667 | if (!utils.hasUserDefinedProperty(options, 'driverInfo')) {
|
668 | options.driverInfo = {
|
669 | name: 'Mongoose',
|
670 | version: pkg.version
|
671 | };
|
672 | }
|
673 |
|
674 | const parsePromise = new Promise((resolve, reject) => {
|
675 | parseConnectionString(uri, options, (err, parsed) => {
|
676 | if (err) {
|
677 | return reject(err);
|
678 | }
|
679 | if (dbName) {
|
680 | this.name = dbName;
|
681 | } else if (parsed.defaultDatabase) {
|
682 | this.name = parsed.defaultDatabase;
|
683 | } else {
|
684 | this.name = get(parsed, 'auth.db', null);
|
685 | }
|
686 | this.host = get(parsed, 'hosts.0.host', 'localhost');
|
687 | this.port = get(parsed, 'hosts.0.port', 27017);
|
688 | this.user = this.user || get(parsed, 'auth.username');
|
689 | this.pass = this.pass || get(parsed, 'auth.password');
|
690 | resolve();
|
691 | });
|
692 | });
|
693 |
|
694 | const _handleReconnect = () => {
|
695 | // If we aren't disconnected, we assume this reconnect is due to a
|
696 | // socket timeout. If there's no activity on a socket for
|
697 | // `socketTimeoutMS`, the driver will attempt to reconnect and emit
|
698 | // this event.
|
699 | if (_this.readyState !== STATES.connected) {
|
700 | _this.readyState = STATES.connected;
|
701 | _this.emit('reconnect');
|
702 | _this.emit('reconnected');
|
703 | }
|
704 | };
|
705 |
|
706 | const promise = new Promise((resolve, reject) => {
|
707 | const client = new mongodb.MongoClient(uri, options);
|
708 | _this.client = client;
|
709 | client.connect(function(error) {
|
710 | if (error) {
|
711 | _this.readyState = STATES.disconnected;
|
712 | return reject(error);
|
713 | }
|
714 |
|
715 | const db = dbName != null ? client.db(dbName) : client.db();
|
716 | _this.db = db;
|
717 |
|
718 | // `useUnifiedTopology` events
|
719 | const type = get(db, 's.topology.s.description.type', '');
|
720 | if (options.useUnifiedTopology) {
|
721 | if (type === 'Single') {
|
722 | const server = Array.from(db.s.topology.s.servers.values())[0];
|
723 |
|
724 | server.s.topology.on('serverHeartbeatSucceeded', () => {
|
725 | _handleReconnect();
|
726 | });
|
727 | server.s.pool.on('reconnect', () => {
|
728 | _handleReconnect();
|
729 | });
|
730 | client.on('serverDescriptionChanged', ev => {
|
731 | const newDescription = ev.newDescription;
|
732 | if (newDescription.type === 'Standalone') {
|
733 | _handleReconnect();
|
734 | } else {
|
735 | _this.readyState = STATES.disconnected;
|
736 | }
|
737 | });
|
738 | } else if (type.startsWith('ReplicaSet')) {
|
739 | client.on('topologyDescriptionChanged', ev => {
|
740 | // Emit disconnected if we've lost connectivity to _all_ servers
|
741 | // in the replica set.
|
742 | const description = ev.newDescription;
|
743 | const servers = Array.from(ev.newDescription.servers.values());
|
744 | const allServersDisconnected = description.type === 'ReplicaSetNoPrimary' &&
|
745 | servers.reduce((cur, d) => cur || d.type === 'Unknown', false);
|
746 | if (_this.readyState === STATES.connected && allServersDisconnected) {
|
747 | // Implicitly emits 'disconnected'
|
748 | _this.readyState = STATES.disconnected;
|
749 | } else if (_this.readyState === STATES.disconnected && !allServersDisconnected) {
|
750 | _handleReconnect();
|
751 | }
|
752 | });
|
753 |
|
754 | db.on('close', function() {
|
755 | const type = get(db, 's.topology.s.description.type', '');
|
756 | if (type !== 'ReplicaSetWithPrimary') {
|
757 | // Implicitly emits 'disconnected'
|
758 | _this.readyState = STATES.disconnected;
|
759 | }
|
760 | });
|
761 | }
|
762 | }
|
763 |
|
764 | // Backwards compat for mongoose 4.x
|
765 | db.on('reconnect', function() {
|
766 | _handleReconnect();
|
767 | });
|
768 | db.s.topology.on('reconnectFailed', function() {
|
769 | _this.emit('reconnectFailed');
|
770 | });
|
771 |
|
772 | if (!options.useUnifiedTopology) {
|
773 | db.s.topology.on('left', function(data) {
|
774 | _this.emit('left', data);
|
775 | });
|
776 | }
|
777 | db.s.topology.on('joined', function(data) {
|
778 | _this.emit('joined', data);
|
779 | });
|
780 | db.s.topology.on('fullsetup', function(data) {
|
781 | _this.emit('fullsetup', data);
|
782 | });
|
783 | if (get(db, 's.topology.s.coreTopology.s.pool') != null) {
|
784 | db.s.topology.s.coreTopology.s.pool.on('attemptReconnect', function() {
|
785 | _this.emit('attemptReconnect');
|
786 | });
|
787 | }
|
788 | if (!options.useUnifiedTopology || !type.startsWith('ReplicaSet')) {
|
789 | db.on('close', function() {
|
790 | // Implicitly emits 'disconnected'
|
791 | _this.readyState = STATES.disconnected;
|
792 | });
|
793 | }
|
794 |
|
795 | if (!options.useUnifiedTopology) {
|
796 | client.on('left', function() {
|
797 | if (_this.readyState === STATES.connected &&
|
798 | get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') {
|
799 | _this.readyState = STATES.disconnected;
|
800 | }
|
801 | });
|
802 | }
|
803 |
|
804 | db.on('timeout', function() {
|
805 | _this.emit('timeout');
|
806 | });
|
807 |
|
808 | delete _this.then;
|
809 | delete _this.catch;
|
810 | _this.readyState = STATES.connected;
|
811 |
|
812 | for (const i in _this.collections) {
|
813 | if (utils.object.hasOwnProperty(_this.collections, i)) {
|
814 | _this.collections[i].onOpen();
|
815 | }
|
816 | }
|
817 |
|
818 | resolve(_this);
|
819 | _this.emit('open');
|
820 | });
|
821 | });
|
822 |
|
823 | const serverSelectionError = new ServerSelectionError();
|
824 | this.$initialConnection = Promise.all([promise, parsePromise]).
|
825 | then(res => res[0]).
|
826 | catch(err => {
|
827 | if (err != null && err.name === 'MongoServerSelectionError') {
|
828 | err = serverSelectionError.assimilateError(err);
|
829 | }
|
830 |
|
831 | if (this.listeners('error').length > 0) {
|
832 | process.nextTick(() => this.emit('error', err));
|
833 | }
|
834 | throw err;
|
835 | });
|
836 | this.then = function(resolve, reject) {
|
837 | return this.$initialConnection.then(resolve, reject);
|
838 | };
|
839 | this.catch = function(reject) {
|
840 | return this.$initialConnection.catch(reject);
|
841 | };
|
842 |
|
843 | if (callback != null) {
|
844 | this.$initialConnection = this.$initialConnection.then(
|
845 | () => callback(null, this),
|
846 | err => callback(err)
|
847 | );
|
848 | }
|
849 |
|
850 | return this;
|
851 | };
|
852 |
|
853 | /*!
|
854 | * ignore
|
855 | */
|
856 |
|
857 | const handleUseMongoClient = function handleUseMongoClient(options) {
|
858 | console.warn('WARNING: The `useMongoClient` option is no longer ' +
|
859 | 'necessary in mongoose 5.x, please remove it.');
|
860 | const stack = new Error().stack;
|
861 | console.warn(stack.substr(stack.indexOf('\n') + 1));
|
862 | delete options.useMongoClient;
|
863 | };
|
864 |
|
865 | /**
|
866 | * Closes the connection
|
867 | *
|
868 | * @param {Boolean} [force] optional
|
869 | * @param {Function} [callback] optional
|
870 | * @return {Promise}
|
871 | * @api public
|
872 | */
|
873 |
|
874 | Connection.prototype.close = function(force, callback) {
|
875 | if (typeof force === 'function') {
|
876 | callback = force;
|
877 | force = false;
|
878 | }
|
879 |
|
880 | this.$wasForceClosed = !!force;
|
881 |
|
882 | return promiseOrCallback(callback, cb => {
|
883 | this._close(force, cb);
|
884 | });
|
885 | };
|
886 |
|
887 | /**
|
888 | * Handles closing the connection
|
889 | *
|
890 | * @param {Boolean} force
|
891 | * @param {Function} callback
|
892 | * @api private
|
893 | */
|
894 | Connection.prototype._close = function(force, callback) {
|
895 | const _this = this;
|
896 | this._closeCalled = true;
|
897 |
|
898 | switch (this.readyState) {
|
899 | case STATES.disconnected:
|
900 | callback();
|
901 | break;
|
902 |
|
903 | case STATES.connected:
|
904 | this.readyState = STATES.disconnecting;
|
905 | this.doClose(force, function(err) {
|
906 | if (err) {
|
907 | return callback(err);
|
908 | }
|
909 | _this.onClose(force);
|
910 | callback(null);
|
911 | });
|
912 |
|
913 | break;
|
914 | case STATES.connecting:
|
915 | this.once('open', function() {
|
916 | _this.close(callback);
|
917 | });
|
918 | break;
|
919 |
|
920 | case STATES.disconnecting:
|
921 | this.once('close', function() {
|
922 | callback();
|
923 | });
|
924 | break;
|
925 | }
|
926 |
|
927 | return this;
|
928 | };
|
929 |
|
930 | /**
|
931 | * Called when the connection closes
|
932 | *
|
933 | * @api private
|
934 | */
|
935 |
|
936 | Connection.prototype.onClose = function(force) {
|
937 | this.readyState = STATES.disconnected;
|
938 |
|
939 | // avoid having the collection subscribe to our event emitter
|
940 | // to prevent 0.3 warning
|
941 | for (const i in this.collections) {
|
942 | if (utils.object.hasOwnProperty(this.collections, i)) {
|
943 | this.collections[i].onClose(force);
|
944 | }
|
945 | }
|
946 |
|
947 | this.emit('close', force);
|
948 | };
|
949 |
|
950 | /**
|
951 | * Retrieves a collection, creating it if not cached.
|
952 | *
|
953 | * Not typically needed by applications. Just talk to your collection through your model.
|
954 | *
|
955 | * @param {String} name of the collection
|
956 | * @param {Object} [options] optional collection options
|
957 | * @return {Collection} collection instance
|
958 | * @api public
|
959 | */
|
960 |
|
961 | Connection.prototype.collection = function(name, options) {
|
962 | options = options ? utils.clone(options) : {};
|
963 | options.$wasForceClosed = this.$wasForceClosed;
|
964 | if (!(name in this.collections)) {
|
965 | this.collections[name] = new Collection(name, this, options);
|
966 | }
|
967 | return this.collections[name];
|
968 | };
|
969 |
|
970 | /**
|
971 | * Declares a plugin executed on all schemas you pass to `conn.model()`
|
972 | *
|
973 | * Equivalent to calling `.plugin(fn)` on each schema you create.
|
974 | *
|
975 | * ####Example:
|
976 | * const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
|
977 | * db.plugin(() => console.log('Applied'));
|
978 | * db.plugins.length; // 1
|
979 | *
|
980 | * db.model('Test', new Schema({})); // Prints "Applied"
|
981 | *
|
982 | * @param {Function} fn plugin callback
|
983 | * @param {Object} [opts] optional options
|
984 | * @return {Connection} this
|
985 | * @see plugins ./plugins.html
|
986 | * @api public
|
987 | */
|
988 |
|
989 | Connection.prototype.plugin = function(fn, opts) {
|
990 | this.plugins.push([fn, opts]);
|
991 | return this;
|
992 | };
|
993 |
|
994 | /**
|
995 | * Defines or retrieves a model.
|
996 | *
|
997 | * var mongoose = require('mongoose');
|
998 | * var db = mongoose.createConnection(..);
|
999 | * db.model('Venue', new Schema(..));
|
1000 | * var Ticket = db.model('Ticket', new Schema(..));
|
1001 | * var Venue = db.model('Venue');
|
1002 | *
|
1003 | * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports.toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._
|
1004 | *
|
1005 | * ####Example:
|
1006 | *
|
1007 | * var schema = new Schema({ name: String }, { collection: 'actor' });
|
1008 | *
|
1009 | * // or
|
1010 | *
|
1011 | * schema.set('collection', 'actor');
|
1012 | *
|
1013 | * // or
|
1014 | *
|
1015 | * var collectionName = 'actor'
|
1016 | * var M = conn.model('Actor', schema, collectionName)
|
1017 | *
|
1018 | * @param {String|Function} name the model name or class extending Model
|
1019 | * @param {Schema} [schema] a schema. necessary when defining a model
|
1020 | * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name
|
1021 | * @see Mongoose#model #index_Mongoose-model
|
1022 | * @return {Model} The compiled model
|
1023 | * @api public
|
1024 | */
|
1025 |
|
1026 | Connection.prototype.model = function(name, schema, collection) {
|
1027 | if (!(this instanceof Connection)) {
|
1028 | throw new MongooseError('`connection.model()` should not be run with ' +
|
1029 | '`new`. If you are doing `new db.model(foo)(bar)`, use ' +
|
1030 | '`db.model(foo)(bar)` instead');
|
1031 | }
|
1032 |
|
1033 | let fn;
|
1034 | if (typeof name === 'function') {
|
1035 | fn = name;
|
1036 | name = fn.name;
|
1037 | }
|
1038 |
|
1039 | // collection name discovery
|
1040 | if (typeof schema === 'string') {
|
1041 | collection = schema;
|
1042 | schema = false;
|
1043 | }
|
1044 |
|
1045 | if (utils.isObject(schema) && !schema.instanceOfSchema) {
|
1046 | schema = new Schema(schema);
|
1047 | }
|
1048 | if (schema && !schema.instanceOfSchema) {
|
1049 | throw new Error('The 2nd parameter to `mongoose.model()` should be a ' +
|
1050 | 'schema or a POJO');
|
1051 | }
|
1052 |
|
1053 | if (this.models[name] && !collection) {
|
1054 | // model exists but we are not subclassing with custom collection
|
1055 | if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) {
|
1056 | throw new MongooseError.OverwriteModelError(name);
|
1057 | }
|
1058 | return this.models[name];
|
1059 | }
|
1060 |
|
1061 | const opts = { cache: false, connection: this };
|
1062 | let model;
|
1063 |
|
1064 | if (schema && schema.instanceOfSchema) {
|
1065 | applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied');
|
1066 |
|
1067 | // compile a model
|
1068 | model = this.base.model(fn || name, schema, collection, opts);
|
1069 |
|
1070 | // only the first model with this name is cached to allow
|
1071 | // for one-offs with custom collection names etc.
|
1072 | if (!this.models[name]) {
|
1073 | this.models[name] = model;
|
1074 | }
|
1075 |
|
1076 | // Errors handled internally, so safe to ignore error
|
1077 | model.init(function $modelInitNoop() {});
|
1078 |
|
1079 | return model;
|
1080 | }
|
1081 |
|
1082 | if (this.models[name] && collection) {
|
1083 | // subclassing current model with alternate collection
|
1084 | model = this.models[name];
|
1085 | schema = model.prototype.schema;
|
1086 | const sub = model.__subclass(this, schema, collection);
|
1087 | // do not cache the sub model
|
1088 | return sub;
|
1089 | }
|
1090 |
|
1091 | // lookup model in mongoose module
|
1092 | model = this.base.models[name];
|
1093 |
|
1094 | if (!model) {
|
1095 | throw new MongooseError.MissingSchemaError(name);
|
1096 | }
|
1097 |
|
1098 | if (this === model.prototype.db
|
1099 | && (!collection || collection === model.collection.name)) {
|
1100 | // model already uses this connection.
|
1101 |
|
1102 | // only the first model with this name is cached to allow
|
1103 | // for one-offs with custom collection names etc.
|
1104 | if (!this.models[name]) {
|
1105 | this.models[name] = model;
|
1106 | }
|
1107 |
|
1108 | return model;
|
1109 | }
|
1110 | this.models[name] = model.__subclass(this, schema, collection);
|
1111 | return this.models[name];
|
1112 | };
|
1113 |
|
1114 | /**
|
1115 | * Removes the model named `name` from this connection, if it exists. You can
|
1116 | * use this function to clean up any models you created in your tests to
|
1117 | * prevent OverwriteModelErrors.
|
1118 | *
|
1119 | * ####Example:
|
1120 | *
|
1121 | * conn.model('User', new Schema({ name: String }));
|
1122 | * console.log(conn.model('User')); // Model object
|
1123 | * conn.deleteModel('User');
|
1124 | * console.log(conn.model('User')); // undefined
|
1125 | *
|
1126 | * // Usually useful in a Mocha `afterEach()` hook
|
1127 | * afterEach(function() {
|
1128 | * conn.deleteModel(/.+/); // Delete every model
|
1129 | * });
|
1130 | *
|
1131 | * @api public
|
1132 | * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
|
1133 | * @return {Connection} this
|
1134 | */
|
1135 |
|
1136 | Connection.prototype.deleteModel = function(name) {
|
1137 | if (typeof name === 'string') {
|
1138 | const model = this.model(name);
|
1139 | if (model == null) {
|
1140 | return this;
|
1141 | }
|
1142 | const collectionName = model.collection.name;
|
1143 | delete this.models[name];
|
1144 | delete this.collections[collectionName];
|
1145 | delete this.base.modelSchemas[name];
|
1146 | } else if (name instanceof RegExp) {
|
1147 | const pattern = name;
|
1148 | const names = this.modelNames();
|
1149 | for (const name of names) {
|
1150 | if (pattern.test(name)) {
|
1151 | this.deleteModel(name);
|
1152 | }
|
1153 | }
|
1154 | } else {
|
1155 | throw new Error('First parameter to `deleteModel()` must be a string ' +
|
1156 | 'or regexp, got "' + name + '"');
|
1157 | }
|
1158 |
|
1159 | return this;
|
1160 | };
|
1161 |
|
1162 | /**
|
1163 | * Watches the entire underlying database for changes. Similar to
|
1164 | * [`Model.watch()`](/docs/api/model.html#model_Model.watch).
|
1165 | *
|
1166 | * This function does **not** trigger any middleware. In particular, it
|
1167 | * does **not** trigger aggregate middleware.
|
1168 | *
|
1169 | * The ChangeStream object is an event emitter that emits the following events:
|
1170 | *
|
1171 | * - 'change': A change occurred, see below example
|
1172 | * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates.
|
1173 | * - 'end': Emitted if the underlying stream is closed
|
1174 | * - 'close': Emitted if the underlying stream is closed
|
1175 | *
|
1176 | * ####Example:
|
1177 | *
|
1178 | * const User = conn.model('User', new Schema({ name: String }));
|
1179 | *
|
1180 | * const changeStream = conn.watch().on('change', data => console.log(data));
|
1181 | *
|
1182 | * // Triggers a 'change' event on the change stream.
|
1183 | * await User.create({ name: 'test' });
|
1184 | *
|
1185 | * @api public
|
1186 | * @param {Array} [pipeline]
|
1187 | * @param {Object} [options] passed without changes to [the MongoDB driver's `Db#watch()` function](https://mongodb.github.io/node-mongodb-native/3.4/api/Db.html#watch)
|
1188 | * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
|
1189 | */
|
1190 |
|
1191 | Connection.prototype.watch = function(pipeline, options) {
|
1192 | const disconnectedError = new MongooseError('Connection ' + this.id +
|
1193 | ' was disconnected when calling `watch()`');
|
1194 |
|
1195 | const changeStreamThunk = cb => {
|
1196 | immediate(() => {
|
1197 | if (this.readyState === STATES.connecting) {
|
1198 | this.once('open', function() {
|
1199 | const driverChangeStream = this.db.watch(pipeline, options);
|
1200 | cb(null, driverChangeStream);
|
1201 | });
|
1202 | } else if (this.readyState === STATES.disconnected && this.db == null) {
|
1203 | cb(disconnectedError);
|
1204 | } else {
|
1205 | const driverChangeStream = this.db.watch(pipeline, options);
|
1206 | cb(null, driverChangeStream);
|
1207 | }
|
1208 | });
|
1209 | };
|
1210 |
|
1211 | const changeStream = new ChangeStream(changeStreamThunk, pipeline, options);
|
1212 | return changeStream;
|
1213 | };
|
1214 |
|
1215 | /**
|
1216 | * Returns an array of model names created on this connection.
|
1217 | * @api public
|
1218 | * @return {Array}
|
1219 | */
|
1220 |
|
1221 | Connection.prototype.modelNames = function() {
|
1222 | return Object.keys(this.models);
|
1223 | };
|
1224 |
|
1225 | /**
|
1226 | * @brief Returns if the connection requires authentication after it is opened. Generally if a
|
1227 | * username and password are both provided than authentication is needed, but in some cases a
|
1228 | * password is not required.
|
1229 | * @api private
|
1230 | * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false.
|
1231 | */
|
1232 | Connection.prototype.shouldAuthenticate = function() {
|
1233 | return this.user != null &&
|
1234 | (this.pass != null || this.authMechanismDoesNotRequirePassword());
|
1235 | };
|
1236 |
|
1237 | /**
|
1238 | * @brief Returns a boolean value that specifies if the current authentication mechanism needs a
|
1239 | * password to authenticate according to the auth objects passed into the openUri methods.
|
1240 | * @api private
|
1241 | * @return {Boolean} true if the authentication mechanism specified in the options object requires
|
1242 | * a password, otherwise false.
|
1243 | */
|
1244 | Connection.prototype.authMechanismDoesNotRequirePassword = function() {
|
1245 | if (this.options && this.options.auth) {
|
1246 | return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0;
|
1247 | }
|
1248 | return true;
|
1249 | };
|
1250 |
|
1251 | /**
|
1252 | * @brief Returns a boolean value that specifies if the provided objects object provides enough
|
1253 | * data to authenticate with. Generally this is true if the username and password are both specified
|
1254 | * but in some authentication methods, a password is not required for authentication so only a username
|
1255 | * is required.
|
1256 | * @param {Object} [options] the options object passed into the openUri methods.
|
1257 | * @api private
|
1258 | * @return {Boolean} true if the provided options object provides enough data to authenticate with,
|
1259 | * otherwise false.
|
1260 | */
|
1261 | Connection.prototype.optionsProvideAuthenticationData = function(options) {
|
1262 | return (options) &&
|
1263 | (options.user) &&
|
1264 | ((options.pass) || this.authMechanismDoesNotRequirePassword());
|
1265 | };
|
1266 |
|
1267 | /**
|
1268 | * Switches to a different database using the same connection pool.
|
1269 | *
|
1270 | * Returns a new connection object, with the new db.
|
1271 | *
|
1272 | * @method useDb
|
1273 | * @memberOf Connection
|
1274 | * @param {String} name The database name
|
1275 | * @param {Object} [options]
|
1276 | * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object.
|
1277 | * @return {Connection} New Connection Object
|
1278 | * @api public
|
1279 | */
|
1280 |
|
1281 | /*!
|
1282 | * Module exports.
|
1283 | */
|
1284 |
|
1285 | Connection.STATES = STATES;
|
1286 | module.exports = Connection;
|