connection.js 48 KB

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