connection.js 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855
  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 MongooseBulkWriteError = require('./error/bulkWriteError');
  10. const MongooseError = require('./error/index');
  11. const ServerSelectionError = require('./error/serverSelection');
  12. const SyncIndexesError = require('./error/syncIndexes');
  13. const applyPlugins = require('./helpers/schema/applyPlugins');
  14. const clone = require('./helpers/clone');
  15. const driver = require('./driver');
  16. const get = require('./helpers/get');
  17. const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
  18. const immediate = require('./helpers/immediate');
  19. const utils = require('./utils');
  20. const CreateCollectionsError = require('./error/createCollectionsError');
  21. const castBulkWrite = require('./helpers/model/castBulkWrite');
  22. const { modelSymbol } = require('./helpers/symbols');
  23. const isPromise = require('./helpers/isPromise');
  24. const decorateBulkWriteResult = require('./helpers/model/decorateBulkWriteResult');
  25. const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
  26. const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
  27. // Snapshot the native Date constructor to ensure both Date.now() and new Date() (and other Date methods)
  28. // bypass timer mocks such as those set up by useFakeTimers().
  29. const Date = globalThis.Date;
  30. /**
  31. * A list of authentication mechanisms that don't require a password for authentication.
  32. * This is used by the authMechanismDoesNotRequirePassword method.
  33. *
  34. * @api private
  35. */
  36. const noPasswordAuthMechanisms = [
  37. 'MONGODB-X509'
  38. ];
  39. /**
  40. * Connection constructor
  41. *
  42. * For practical reasons, a Connection equals a Db.
  43. *
  44. * @param {Mongoose} base a mongoose instance
  45. * @inherits NodeJS EventEmitter https://nodejs.org/api/events.html#class-eventemitter
  46. * @event `connecting`: Emitted when `connection.openUri()` is executed on this connection.
  47. * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios.
  48. * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connection's models.
  49. * @event `disconnecting`: Emitted when `connection.close()` was executed.
  50. * @event `disconnected`: Emitted after getting disconnected from the db.
  51. * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connection's models.
  52. * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successful connection.
  53. * @event `error`: Emitted when an error occurs on this connection.
  54. * @event `operation-start`: Emitted when a call to the MongoDB Node.js driver, like a `find()` or `insertOne()`, happens on any collection tied to this connection.
  55. * @event `operation-end`: Emitted when a call to the MongoDB Node.js driver, like a `find()` or `insertOne()`, either succeeds or errors.
  56. * @api public
  57. */
  58. function Connection(base) {
  59. this.base = base;
  60. this.collections = {};
  61. this.models = {};
  62. this.config = {};
  63. this.replica = false;
  64. this.options = null;
  65. this.otherDbs = []; // FIXME: To be replaced with relatedDbs
  66. this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection
  67. this.states = STATES;
  68. this._readyState = STATES.disconnected;
  69. this._closeCalled = false;
  70. this._hasOpened = false;
  71. this.plugins = [];
  72. if (typeof base === 'undefined' || !base.connections.length) {
  73. this.id = 0;
  74. } else {
  75. this.id = base.nextConnectionId;
  76. }
  77. // Internal queue of objects `{ fn, ctx, args }` that Mongoose calls when this connection is successfully
  78. // opened. In `onOpen()`, Mongoose calls every entry in `_queue` and empties the queue.
  79. this._queue = [];
  80. }
  81. /*!
  82. * Inherit from EventEmitter
  83. */
  84. Object.setPrototypeOf(Connection.prototype, EventEmitter.prototype);
  85. /**
  86. * Connection ready state
  87. *
  88. * - 0 = disconnected
  89. * - 1 = connected
  90. * - 2 = connecting
  91. * - 3 = disconnecting
  92. *
  93. * Each state change emits its associated event name.
  94. *
  95. * #### Example:
  96. *
  97. * conn.on('connected', callback);
  98. * conn.on('disconnected', callback);
  99. *
  100. * @property readyState
  101. * @memberOf Connection
  102. * @instance
  103. * @api public
  104. */
  105. Object.defineProperty(Connection.prototype, 'readyState', {
  106. get: function() {
  107. // If connection thinks it is connected, but we haven't received a heartbeat in 2 heartbeat intervals,
  108. // that likely means the connection is stale (potentially due to frozen AWS Lambda container)
  109. if (
  110. this._readyState === STATES.connected &&
  111. this._lastHeartbeatAt != null &&
  112. // LoadBalanced topology (behind haproxy, including Atlas serverless instances) don't use heartbeats,
  113. // so we can't use this check in that case.
  114. this.client?.topology?.s?.description?.type !== 'LoadBalanced' &&
  115. typeof this.client?.topology?.s?.description?.heartbeatFrequencyMS === 'number' &&
  116. Date.now() - this._lastHeartbeatAt >= this.client.topology.s.description.heartbeatFrequencyMS * 2) {
  117. return STATES.disconnected;
  118. }
  119. return this._readyState;
  120. },
  121. set: function(val) {
  122. if (!(val in STATES)) {
  123. throw new Error('Invalid connection state: ' + val);
  124. }
  125. if (this._readyState !== val) {
  126. this._readyState = val;
  127. // [legacy] loop over the otherDbs on this connection and change their state
  128. for (const db of this.otherDbs) {
  129. db.readyState = val;
  130. }
  131. if (STATES.connected === val) {
  132. this._hasOpened = true;
  133. }
  134. this.emit(STATES[val]);
  135. }
  136. }
  137. });
  138. /**
  139. * Gets the value of the option `key`. Equivalent to `conn.options[key]`
  140. *
  141. * #### Example:
  142. *
  143. * conn.get('test'); // returns the 'test' value
  144. *
  145. * @param {String} key
  146. * @method get
  147. * @api public
  148. */
  149. Connection.prototype.get = function getOption(key) {
  150. if (Object.hasOwn(this.config, key)) {
  151. return this.config[key];
  152. }
  153. return get(this.options, key);
  154. };
  155. /**
  156. * Sets the value of the option `key`. Equivalent to `conn.options[key] = val`
  157. *
  158. * Supported options include:
  159. *
  160. * - `maxTimeMS`: Set [`maxTimeMS`](https://mongoosejs.com/docs/api/query.html#Query.prototype.maxTimeMS()) for all queries on this connection.
  161. * - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
  162. *
  163. * #### Example:
  164. *
  165. * conn.set('test', 'foo');
  166. * conn.get('test'); // 'foo'
  167. * conn.options.test; // 'foo'
  168. *
  169. * @param {String} key
  170. * @param {Any} val
  171. * @method set
  172. * @api public
  173. */
  174. Connection.prototype.set = function setOption(key, val) {
  175. if (Object.hasOwn(this.config, key)) {
  176. this.config[key] = val;
  177. return val;
  178. }
  179. this.options = this.options || {};
  180. this.options[key] = val;
  181. return val;
  182. };
  183. /**
  184. * A hash of the collections associated with this connection
  185. *
  186. * @property collections
  187. * @memberOf Connection
  188. * @instance
  189. * @api public
  190. */
  191. Connection.prototype.collections;
  192. /**
  193. * The name of the database this connection points to.
  194. *
  195. * #### Example:
  196. *
  197. * mongoose.createConnection('mongodb://127.0.0.1:27017/mydb').name; // "mydb"
  198. *
  199. * @property name
  200. * @memberOf Connection
  201. * @instance
  202. * @api public
  203. */
  204. Connection.prototype.name;
  205. /**
  206. * A [POJO](https://masteringjs.io/tutorials/fundamentals/pojo) containing
  207. * a map from model names to models. Contains all models that have been
  208. * added to this connection using [`Connection#model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()).
  209. *
  210. * #### Example:
  211. *
  212. * const conn = mongoose.createConnection();
  213. * const Test = conn.model('Test', mongoose.Schema({ name: String }));
  214. *
  215. * Object.keys(conn.models).length; // 1
  216. * conn.models.Test === Test; // true
  217. *
  218. * @property models
  219. * @memberOf Connection
  220. * @instance
  221. * @api public
  222. */
  223. Connection.prototype.models;
  224. /**
  225. * A number identifier for this connection. Used for debugging when
  226. * you have [multiple connections](https://mongoosejs.com/docs/connections.html#multiple_connections).
  227. *
  228. * #### Example:
  229. *
  230. * // The default connection has `id = 0`
  231. * mongoose.connection.id; // 0
  232. *
  233. * // If you create a new connection, Mongoose increments id
  234. * const conn = mongoose.createConnection();
  235. * conn.id; // 1
  236. *
  237. * @property id
  238. * @memberOf Connection
  239. * @instance
  240. * @api public
  241. */
  242. Connection.prototype.id;
  243. /**
  244. * The plugins that will be applied to all models created on this connection.
  245. *
  246. * #### Example:
  247. *
  248. * const db = mongoose.createConnection('mongodb://127.0.0.1:27017/mydb');
  249. * db.plugin(() => console.log('Applied'));
  250. * db.plugins.length; // 1
  251. *
  252. * db.model('Test', new Schema({})); // Prints "Applied"
  253. *
  254. * @property plugins
  255. * @memberOf Connection
  256. * @instance
  257. * @api public
  258. */
  259. Object.defineProperty(Connection.prototype, 'plugins', {
  260. configurable: false,
  261. enumerable: true,
  262. writable: true
  263. });
  264. /**
  265. * The host name portion of the URI. If multiple hosts, such as a replica set,
  266. * this will contain the first host name in the URI
  267. *
  268. * #### Example:
  269. *
  270. * mongoose.createConnection('mongodb://127.0.0.1:27017/mydb').host; // "127.0.0.1"
  271. *
  272. * @property host
  273. * @memberOf Connection
  274. * @instance
  275. * @api public
  276. */
  277. Object.defineProperty(Connection.prototype, 'host', {
  278. configurable: true,
  279. enumerable: true,
  280. writable: true
  281. });
  282. /**
  283. * The port portion of the URI. If multiple hosts, such as a replica set,
  284. * this will contain the port from the first host name in the URI.
  285. *
  286. * #### Example:
  287. *
  288. * mongoose.createConnection('mongodb://127.0.0.1:27017/mydb').port; // 27017
  289. *
  290. * @property port
  291. * @memberOf Connection
  292. * @instance
  293. * @api public
  294. */
  295. Object.defineProperty(Connection.prototype, 'port', {
  296. configurable: true,
  297. enumerable: true,
  298. writable: true
  299. });
  300. /**
  301. * The username specified in the URI
  302. *
  303. * #### Example:
  304. *
  305. * mongoose.createConnection('mongodb://val:psw@127.0.0.1:27017/mydb').user; // "val"
  306. *
  307. * @property user
  308. * @memberOf Connection
  309. * @instance
  310. * @api public
  311. */
  312. Object.defineProperty(Connection.prototype, 'user', {
  313. configurable: true,
  314. enumerable: true,
  315. writable: true
  316. });
  317. /**
  318. * The password specified in the URI
  319. *
  320. * #### Example:
  321. *
  322. * mongoose.createConnection('mongodb://val:psw@127.0.0.1:27017/mydb').pass; // "psw"
  323. *
  324. * @property pass
  325. * @memberOf Connection
  326. * @instance
  327. * @api public
  328. */
  329. Object.defineProperty(Connection.prototype, 'pass', {
  330. configurable: true,
  331. enumerable: true,
  332. writable: true
  333. });
  334. /**
  335. * The mongodb.Db instance, set when the connection is opened
  336. *
  337. * @property db
  338. * @memberOf Connection
  339. * @instance
  340. * @api public
  341. */
  342. Connection.prototype.db;
  343. /**
  344. * The MongoClient instance this connection uses to talk to MongoDB. Mongoose automatically sets this property
  345. * when the connection is opened.
  346. *
  347. * @property client
  348. * @memberOf Connection
  349. * @instance
  350. * @api public
  351. */
  352. Connection.prototype.client;
  353. /**
  354. * A hash of the global options that are associated with this connection
  355. *
  356. * @property config
  357. * @memberOf Connection
  358. * @instance
  359. * @api public
  360. */
  361. Connection.prototype.config;
  362. /**
  363. * Helper for `createCollection()`. Will explicitly create the given collection
  364. * with specified options. Used to create [capped collections](https://www.mongodb.com/docs/manual/core/capped-collections/)
  365. * and [views](https://www.mongodb.com/docs/manual/core/views/) from mongoose.
  366. *
  367. * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
  368. *
  369. * @method createCollection
  370. * @param {string} collection The collection to create
  371. * @param {Object} [options] see [MongoDB driver docs](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
  372. * @return {Promise}
  373. * @api public
  374. */
  375. Connection.prototype.createCollection = async function createCollection(collection, options) {
  376. if (typeof options === 'function' || (arguments.length >= 3 && typeof arguments[2] === 'function')) {
  377. throw new MongooseError('Connection.prototype.createCollection() no longer accepts a callback');
  378. }
  379. await this._waitForConnect();
  380. return this.db.createCollection(collection, options);
  381. };
  382. /**
  383. * _Requires MongoDB Server 8.0 or greater_. Executes bulk write operations across multiple models in a single operation.
  384. * You must specify the `model` for each operation: Mongoose will use `model` for casting and validation, as well as
  385. * determining which collection to apply the operation to.
  386. *
  387. * #### Example:
  388. * const Test = mongoose.model('Test', new Schema({ name: String }));
  389. *
  390. * await db.bulkWrite([
  391. * { model: Test, name: 'insertOne', document: { name: 'test1' } }, // Can specify model as a Model class...
  392. * { model: 'Test', name: 'insertOne', document: { name: 'test2' } } // or as a model name
  393. * ], { ordered: false });
  394. *
  395. * @method bulkWrite
  396. * @param {Array} ops
  397. * @param {Object} [options]
  398. * @param {Boolean} [options.ordered] If false, perform unordered operations. If true, perform ordered operations.
  399. * @param {Session} [options.session] The session to use for the operation.
  400. * @return {Promise}
  401. * @see MongoDB https://www.mongodb.com/docs/manual/reference/command/bulkWrite/#mongodb-dbcommand-dbcmd.bulkWrite
  402. * @api public
  403. */
  404. Connection.prototype.bulkWrite = async function bulkWrite(ops, options) {
  405. await this._waitForConnect();
  406. options = options || {};
  407. const ordered = options.ordered == null ? true : options.ordered;
  408. const asyncLocalStorage = this.base.transactionAsyncLocalStorage?.getStore();
  409. if ((!options || !Object.hasOwn(options, 'session')) && asyncLocalStorage?.session != null) {
  410. options = { ...options, session: asyncLocalStorage.session };
  411. }
  412. const now = this.base.now();
  413. let res = null;
  414. if (ordered) {
  415. const opsToSend = [];
  416. for (const op of ops) {
  417. if (typeof op.model !== 'string' && !op.model?.[modelSymbol]) {
  418. throw new MongooseError('Must specify model in Connection.prototype.bulkWrite() operations');
  419. }
  420. const Model = op.model[modelSymbol] ? op.model : this.model(op.model);
  421. if (op.name == null) {
  422. throw new MongooseError('Must specify operation name in Connection.prototype.bulkWrite()');
  423. }
  424. if (!Object.hasOwn(castBulkWrite.cast, op.name)) {
  425. throw new MongooseError(`Unrecognized bulkWrite() operation name ${op.name}`);
  426. }
  427. await castBulkWrite.cast[op.name](Model, op, options, now);
  428. opsToSend.push({ ...op, namespace: Model.namespace() });
  429. }
  430. res = await this.client.bulkWrite(opsToSend, options);
  431. } else {
  432. const validOps = [];
  433. const validOpIndexes = [];
  434. let validationErrors = [];
  435. const asyncValidations = [];
  436. const results = [];
  437. for (let i = 0; i < ops.length; ++i) {
  438. const op = ops[i];
  439. if (typeof op.model !== 'string' && !op.model?.[modelSymbol]) {
  440. const error = new MongooseError('Must specify model in Connection.prototype.bulkWrite() operations');
  441. validationErrors.push({ index: i, error: error });
  442. results[i] = error;
  443. continue;
  444. }
  445. let Model;
  446. try {
  447. Model = op.model[modelSymbol] ? op.model : this.model(op.model);
  448. } catch (error) {
  449. validationErrors.push({ index: i, error: error });
  450. continue;
  451. }
  452. if (op.name == null) {
  453. const error = new MongooseError('Must specify operation name in Connection.prototype.bulkWrite()');
  454. validationErrors.push({ index: i, error: error });
  455. results[i] = error;
  456. continue;
  457. }
  458. if (!Object.hasOwn(castBulkWrite.cast, op.name)) {
  459. const error = new MongooseError(`Unrecognized bulkWrite() operation name ${op.name}`);
  460. validationErrors.push({ index: i, error: error });
  461. results[i] = error;
  462. continue;
  463. }
  464. let maybePromise = null;
  465. try {
  466. maybePromise = castBulkWrite.cast[op.name](Model, op, options, now);
  467. } catch (error) {
  468. validationErrors.push({ index: i, error: error });
  469. results[i] = error;
  470. continue;
  471. }
  472. if (isPromise(maybePromise)) {
  473. asyncValidations.push(
  474. maybePromise.then(
  475. () => {
  476. validOps.push({ ...op, namespace: Model.namespace() });
  477. validOpIndexes.push(i);
  478. },
  479. error => {
  480. validationErrors.push({ index: i, error: error });
  481. results[i] = error;
  482. }
  483. )
  484. );
  485. } else {
  486. validOps.push({ ...op, namespace: Model.namespace() });
  487. validOpIndexes.push(i);
  488. }
  489. }
  490. if (asyncValidations.length > 0) {
  491. await Promise.all(asyncValidations);
  492. }
  493. validationErrors = validationErrors.
  494. sort((v1, v2) => v1.index - v2.index).
  495. map(v => v.error);
  496. if (validOps.length === 0) {
  497. if (options.throwOnValidationError && validationErrors.length) {
  498. throw new MongooseBulkWriteError(
  499. validationErrors,
  500. results,
  501. res,
  502. 'bulkWrite'
  503. );
  504. }
  505. const BulkWriteResult = this.base.driver.get().BulkWriteResult;
  506. const res = new BulkWriteResult(getDefaultBulkwriteResult(), false);
  507. return decorateBulkWriteResult(res, validationErrors, results);
  508. }
  509. let error;
  510. [res, error] = await this.client.bulkWrite(validOps, options).
  511. then(res => ([res, null])).
  512. catch(err => ([null, err]));
  513. for (let i = 0; i < validOpIndexes.length; ++i) {
  514. results[validOpIndexes[i]] = null;
  515. }
  516. if (error) {
  517. if (validationErrors.length > 0) {
  518. decorateBulkWriteResult(error, validationErrors, results);
  519. error.mongoose = error.mongoose || {};
  520. error.mongoose.validationErrors = validationErrors;
  521. }
  522. }
  523. if (validationErrors.length > 0) {
  524. if (options.throwOnValidationError) {
  525. throw new MongooseBulkWriteError(
  526. validationErrors,
  527. results,
  528. res,
  529. 'bulkWrite'
  530. );
  531. } else {
  532. decorateBulkWriteResult(res, validationErrors, results);
  533. }
  534. }
  535. }
  536. return res;
  537. };
  538. /**
  539. * Calls `createCollection()` on a models in a series.
  540. *
  541. * @method createCollections
  542. * @param {Boolean} continueOnError When true, will continue to create collections and create a new error class for the collections that errored.
  543. * @returns {Promise}
  544. * @api public
  545. */
  546. Connection.prototype.createCollections = async function createCollections(options = {}) {
  547. const result = {};
  548. const errorsMap = { };
  549. const { continueOnError } = options;
  550. delete options.continueOnError;
  551. for (const model of Object.values(this.models)) {
  552. try {
  553. result[model.modelName] = await model.createCollection({});
  554. } catch (err) {
  555. if (!continueOnError) {
  556. errorsMap[model.modelName] = err;
  557. break;
  558. } else {
  559. result[model.modelName] = err;
  560. }
  561. }
  562. }
  563. if (!continueOnError && utils.hasOwnKeys(errorsMap)) {
  564. const message = Object.entries(errorsMap).map(([modelName, err]) => `${modelName}: ${err.message}`).join(', ');
  565. const createCollectionsError = new CreateCollectionsError(message, errorsMap);
  566. throw createCollectionsError;
  567. }
  568. return result;
  569. };
  570. /**
  571. * A convenience wrapper for `connection.client.withSession()`.
  572. *
  573. * #### Example:
  574. *
  575. * await conn.withSession(async session => {
  576. * const doc = await TestModel.findOne().session(session);
  577. * });
  578. *
  579. * @method withSession
  580. * @param {Function} executor called with 1 argument: a `ClientSession` instance
  581. * @return {Promise} resolves to the return value of the executor function
  582. * @api public
  583. */
  584. Connection.prototype.withSession = async function withSession(executor) {
  585. if (arguments.length === 0) {
  586. throw new Error('Please provide an executor function');
  587. }
  588. return await this.client.withSession(executor);
  589. };
  590. /**
  591. * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
  592. * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
  593. * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
  594. *
  595. * #### Example:
  596. *
  597. * const session = await conn.startSession();
  598. * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
  599. * await doc.deleteOne();
  600. * // `doc` will always be null, even if reading from a replica set
  601. * // secondary. Without causal consistency, it is possible to
  602. * // get a doc back from the below query if the query reads from a
  603. * // secondary that is experiencing replication lag.
  604. * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
  605. *
  606. *
  607. * @method startSession
  608. * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession)
  609. * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
  610. * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
  611. * @api public
  612. */
  613. Connection.prototype.startSession = async function startSession(options) {
  614. if (arguments.length >= 2 && typeof arguments[1] === 'function') {
  615. throw new MongooseError('Connection.prototype.startSession() no longer accepts a callback');
  616. }
  617. await this._waitForConnect();
  618. const session = this.client.startSession(options);
  619. return session;
  620. };
  621. /**
  622. * _Requires MongoDB >= 3.6.0._ Executes the wrapped async function
  623. * in a transaction. Mongoose will commit the transaction if the
  624. * async function executes successfully and attempt to retry if
  625. * there was a retriable error.
  626. *
  627. * Calls the MongoDB driver's [`session.withTransaction()`](https://mongodb.github.io/node-mongodb-native/4.9/classes/ClientSession.html#withTransaction),
  628. * but also handles resetting Mongoose document state as shown below.
  629. *
  630. * #### Example:
  631. *
  632. * const doc = new Person({ name: 'Will Riker' });
  633. * await db.transaction(async function setRank(session) {
  634. * doc.rank = 'Captain';
  635. * await doc.save({ session });
  636. * doc.isNew; // false
  637. *
  638. * // Throw an error to abort the transaction
  639. * throw new Error('Oops!');
  640. * },{ readPreference: 'primary' }).catch(() => {});
  641. *
  642. * // true, `transaction()` reset the document's state because the
  643. * // transaction was aborted.
  644. * doc.isNew;
  645. *
  646. * @method transaction
  647. * @param {Function} fn Function to execute in a transaction
  648. * @param {mongodb.TransactionOptions} [options] Optional settings for the transaction
  649. * @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.
  650. * @api public
  651. */
  652. Connection.prototype.transaction = function transaction(fn, options) {
  653. return this.startSession().then(session => {
  654. session[sessionNewDocuments] = new Map();
  655. return session.withTransaction(() => _wrapUserTransaction(fn, session, this.base), options).
  656. then(res => {
  657. delete session[sessionNewDocuments];
  658. return res;
  659. }).
  660. catch(err => {
  661. delete session[sessionNewDocuments];
  662. throw err;
  663. }).
  664. finally(() => {
  665. session.endSession().catch(() => {});
  666. });
  667. });
  668. };
  669. /*!
  670. * Reset document state in between transaction retries re: gh-13698
  671. */
  672. async function _wrapUserTransaction(fn, session, mongoose) {
  673. try {
  674. const res = mongoose.transactionAsyncLocalStorage == null
  675. ? await fn(session)
  676. : await new Promise(resolve => {
  677. mongoose.transactionAsyncLocalStorage.run(
  678. { session },
  679. () => resolve(fn(session))
  680. );
  681. });
  682. return res;
  683. } catch (err) {
  684. _resetSessionDocuments(session);
  685. throw err;
  686. }
  687. }
  688. /*!
  689. * If transaction was aborted, we need to reset newly inserted documents' `isNew`.
  690. */
  691. function _resetSessionDocuments(session) {
  692. for (const doc of session[sessionNewDocuments].keys()) {
  693. const state = session[sessionNewDocuments].get(doc);
  694. if (Object.hasOwn(state, 'isNew')) {
  695. doc.$isNew = state.isNew;
  696. }
  697. if (Object.hasOwn(state, 'versionKey')) {
  698. doc.set(doc.schema.options.versionKey, state.versionKey);
  699. }
  700. if (state.modifiedPaths.length > 0 && doc.$__.activePaths.states.modify == null) {
  701. doc.$__.activePaths.states.modify = {};
  702. }
  703. for (const path of state.modifiedPaths) {
  704. const currentState = doc.$__.activePaths.paths[path];
  705. if (currentState != null) {
  706. delete doc.$__.activePaths[currentState][path];
  707. }
  708. doc.$__.activePaths.paths[path] = 'modify';
  709. doc.$__.activePaths.states.modify[path] = true;
  710. }
  711. for (const path of state.atomics.keys()) {
  712. const val = doc.$__getValue(path);
  713. if (val == null) {
  714. continue;
  715. }
  716. val[arrayAtomicsSymbol] = state.atomics.get(path);
  717. }
  718. }
  719. }
  720. /**
  721. * Helper for `dropCollection()`. Will delete the given collection, including
  722. * all documents and indexes.
  723. *
  724. * @method dropCollection
  725. * @param {string} collection The collection to delete
  726. * @return {Promise}
  727. * @api public
  728. */
  729. Connection.prototype.dropCollection = async function dropCollection(collection) {
  730. if (arguments.length >= 2 && typeof arguments[1] === 'function') {
  731. throw new MongooseError('Connection.prototype.dropCollection() no longer accepts a callback');
  732. }
  733. await this._waitForConnect();
  734. return this.db.dropCollection(collection);
  735. };
  736. /**
  737. * Waits for connection to be established, so the connection has a `client`
  738. *
  739. * @param {Boolean} [noTimeout=false] if set, don't put a timeout on the operation. Used internally so `mongoose.model()` doesn't leave open handles.
  740. * @return Promise
  741. * @api private
  742. */
  743. Connection.prototype._waitForConnect = async function _waitForConnect(noTimeout) {
  744. if ((this.readyState === STATES.connecting || this.readyState === STATES.disconnected) && this._shouldBufferCommands()) {
  745. const bufferTimeoutMS = this._getBufferTimeoutMS();
  746. let timeout = null;
  747. let timedOut = false;
  748. // The element that this function pushes onto `_queue`, stored to make it easy to remove later
  749. const queueElement = {};
  750. // Mongoose executes all elements in `_queue` when initial connection succeeds in `onOpen()`.
  751. const waitForConnectPromise = new Promise(resolve => {
  752. queueElement.fn = resolve;
  753. this._queue.push(queueElement);
  754. });
  755. if (noTimeout) {
  756. await waitForConnectPromise;
  757. } else {
  758. await Promise.race([
  759. waitForConnectPromise,
  760. new Promise(resolve => {
  761. timeout = setTimeout(
  762. () => {
  763. timedOut = true;
  764. resolve();
  765. },
  766. bufferTimeoutMS
  767. );
  768. })
  769. ]);
  770. }
  771. if (timedOut) {
  772. const index = this._queue.indexOf(queueElement);
  773. if (index !== -1) {
  774. this._queue.splice(index, 1);
  775. }
  776. const message = 'Connection operation buffering timed out after ' + bufferTimeoutMS + 'ms';
  777. throw new MongooseError(message);
  778. } else if (timeout != null) {
  779. // Not strictly necessary, but avoid the extra overhead of creating a new MongooseError
  780. // in case of success
  781. clearTimeout(timeout);
  782. }
  783. }
  784. };
  785. /*!
  786. * Get the default buffer timeout for this connection
  787. */
  788. Connection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
  789. if (this.config.bufferTimeoutMS != null) {
  790. return this.config.bufferTimeoutMS;
  791. }
  792. if (this.base?.get('bufferTimeoutMS') != null) {
  793. return this.base.get('bufferTimeoutMS');
  794. }
  795. return 10000;
  796. };
  797. /**
  798. * Helper for MongoDB Node driver's `listCollections()`.
  799. * Returns an array of collection objects.
  800. *
  801. * @method listCollections
  802. * @return {Promise<Collection[]>}
  803. * @api public
  804. */
  805. Connection.prototype.listCollections = async function listCollections() {
  806. await this._waitForConnect();
  807. const cursor = this.db.listCollections();
  808. return await cursor.toArray();
  809. };
  810. /**
  811. * Helper for MongoDB Node driver's `listDatabases()`.
  812. * Returns an object with a `databases` property that contains an
  813. * array of database objects.
  814. *
  815. * #### Example:
  816. * const { databases } = await mongoose.connection.listDatabases();
  817. * databases; // [{ name: 'mongoose_test', sizeOnDisk: 0, empty: false }]
  818. *
  819. * @method listCollections
  820. * @return {Promise<{ databases: Array<{ name: string }> }>}
  821. * @api public
  822. */
  823. Connection.prototype.listDatabases = async function listDatabases() {
  824. // Implemented in `lib/drivers/node-mongodb-native/connection.js`
  825. throw new MongooseError('listDatabases() not implemented by driver');
  826. };
  827. /**
  828. * Helper for `dropDatabase()`. Deletes the given database, including all
  829. * collections, documents, and indexes.
  830. *
  831. * #### Example:
  832. *
  833. * const conn = mongoose.createConnection('mongodb://127.0.0.1:27017/mydb');
  834. * // Deletes the entire 'mydb' database
  835. * await conn.dropDatabase();
  836. *
  837. * @method dropDatabase
  838. * @return {Promise}
  839. * @api public
  840. */
  841. Connection.prototype.dropDatabase = async function dropDatabase() {
  842. if (arguments.length >= 1 && typeof arguments[0] === 'function') {
  843. throw new MongooseError('Connection.prototype.dropDatabase() no longer accepts a callback');
  844. }
  845. await this._waitForConnect();
  846. // If `dropDatabase()` is called, this model's collection will not be
  847. // init-ed. It is sufficiently common to call `dropDatabase()` after
  848. // `mongoose.connect()` but before creating models that we want to
  849. // support this. See gh-6796
  850. for (const model of Object.values(this.models)) {
  851. delete model.$init;
  852. }
  853. return this.db.dropDatabase();
  854. };
  855. /*!
  856. * ignore
  857. */
  858. Connection.prototype._shouldBufferCommands = function _shouldBufferCommands() {
  859. if (this.config.bufferCommands != null) {
  860. return this.config.bufferCommands;
  861. }
  862. if (this.base.get('bufferCommands') != null) {
  863. return this.base.get('bufferCommands');
  864. }
  865. return true;
  866. };
  867. /**
  868. * error
  869. *
  870. * Graceful error handling, passes error to callback
  871. * if available, else emits error on the connection.
  872. *
  873. * @param {Error} err
  874. * @param {Function} callback optional
  875. * @emits "error" Emits the `error` event with the given `err`, unless a callback is specified
  876. * @returns {Promise|null} Returns a rejected Promise if no `callback` is given.
  877. * @api private
  878. */
  879. Connection.prototype.error = function error(err, callback) {
  880. if (callback) {
  881. callback(err);
  882. return null;
  883. }
  884. if (this.listeners('error').length > 0) {
  885. this.emit('error', err);
  886. }
  887. return Promise.reject(err);
  888. };
  889. /**
  890. * Called when the connection is opened
  891. *
  892. * @emits "open"
  893. * @api private
  894. */
  895. Connection.prototype.onOpen = function() {
  896. this.readyState = STATES.connected;
  897. for (const d of this._queue) {
  898. d.fn.apply(d.ctx, d.args);
  899. }
  900. this._queue = [];
  901. // avoid having the collection subscribe to our event emitter
  902. // to prevent 0.3 warning
  903. for (const i in this.collections) {
  904. if (Object.hasOwn(this.collections, i)) {
  905. this.collections[i].onOpen();
  906. }
  907. }
  908. this.emit('open');
  909. };
  910. /**
  911. * Opens the connection with a URI using `MongoClient.connect()`.
  912. *
  913. * @param {String} uri The URI to connect with.
  914. * @param {Object} [options] Passed on to [`MongoClient.connect`](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#connect-1)
  915. * @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.
  916. * @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.
  917. * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string.
  918. * @param {String} [options.user] username for authentication, equivalent to `options.auth.username`. Maintained for backwards compatibility.
  919. * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
  920. * @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).
  921. * @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).
  922. * @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).
  923. * @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.
  924. * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
  925. * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary).
  926. * @param {Number} [options.socketTimeoutMS=0] 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. `socketTimeoutMS` defaults to 0, which means Node.js will not time out the socket due to inactivity. 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.
  927. * @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.
  928. * @param {Boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection.
  929. * @returns {Promise<Connection>}
  930. * @api public
  931. */
  932. Connection.prototype.openUri = async function openUri(uri, options) {
  933. if (this.readyState === STATES.connecting || this.readyState === STATES.connected) {
  934. if (this._connectionString === uri) {
  935. return this;
  936. }
  937. }
  938. this._closeCalled = false;
  939. // Internal option to skip `await this.$initialConnection` in
  940. // this function for `createConnection()`. Because otherwise
  941. // `createConnection()` would have an uncatchable error.
  942. let _fireAndForget = false;
  943. if (options && '_fireAndForget' in options) {
  944. _fireAndForget = options._fireAndForget;
  945. delete options._fireAndForget;
  946. }
  947. try {
  948. _validateArgs.apply(arguments);
  949. } catch (err) {
  950. if (_fireAndForget) {
  951. throw err;
  952. }
  953. this.$initialConnection = Promise.reject(err);
  954. throw err;
  955. }
  956. this.$initialConnection = this.createClient(uri, options).
  957. then(() => this).
  958. catch(err => {
  959. this.readyState = STATES.disconnected;
  960. if (this.listeners('error').length > 0) {
  961. immediate(() => this.emit('error', err));
  962. }
  963. throw err;
  964. });
  965. for (const model of Object.values(this.models)) {
  966. // Errors handled internally, so safe to ignore error
  967. model.init().catch(function $modelInitNoop() {});
  968. }
  969. // `createConnection()` calls this `openUri()` function without
  970. // awaiting on the result, so we set this option to rely on
  971. // `asPromise()` to handle any errors.
  972. if (_fireAndForget) {
  973. return this;
  974. }
  975. try {
  976. await this.$initialConnection;
  977. } catch (err) {
  978. throw _handleConnectionErrors(err);
  979. }
  980. return this;
  981. };
  982. /**
  983. * Listen to events in the Connection
  984. *
  985. * @param {String} event The event to listen on
  986. * @param {Function} callback
  987. * @see Connection#readyState https://mongoosejs.com/docs/api/connection.html#Connection.prototype.readyState
  988. *
  989. * @method on
  990. * @instance
  991. * @memberOf Connection
  992. * @api public
  993. */
  994. // Treat `on('error')` handlers as handling the initialConnection promise
  995. // to avoid uncaught exceptions when using `on('error')`. See gh-14377.
  996. Connection.prototype.on = function on(event, callback) {
  997. if (event === 'error' && this.$initialConnection) {
  998. this.$initialConnection.catch(() => {});
  999. }
  1000. return EventEmitter.prototype.on.call(this, event, callback);
  1001. };
  1002. /**
  1003. * Listen to a event once in the Connection
  1004. *
  1005. * @param {String} event The event to listen on
  1006. * @param {Function} callback
  1007. * @see Connection#readyState https://mongoosejs.com/docs/api/connection.html#Connection.prototype.readyState
  1008. *
  1009. * @method once
  1010. * @instance
  1011. * @memberOf Connection
  1012. * @api public
  1013. */
  1014. // Treat `on('error')` handlers as handling the initialConnection promise
  1015. // to avoid uncaught exceptions when using `on('error')`. See gh-14377.
  1016. Connection.prototype.once = function on(event, callback) {
  1017. if (event === 'error' && this.$initialConnection) {
  1018. this.$initialConnection.catch(() => {});
  1019. }
  1020. return EventEmitter.prototype.once.call(this, event, callback);
  1021. };
  1022. /*!
  1023. * ignore
  1024. */
  1025. function _validateArgs(uri, options, callback) {
  1026. if (typeof options === 'function' && callback == null) {
  1027. throw new MongooseError('Connection.prototype.openUri() no longer accepts a callback');
  1028. } else if (typeof callback === 'function') {
  1029. throw new MongooseError('Connection.prototype.openUri() no longer accepts a callback');
  1030. }
  1031. }
  1032. /*!
  1033. * ignore
  1034. */
  1035. function _handleConnectionErrors(err) {
  1036. if (err?.name === 'MongoServerSelectionError') {
  1037. const originalError = err;
  1038. err = new ServerSelectionError();
  1039. err.assimilateError(originalError);
  1040. }
  1041. return err;
  1042. }
  1043. /**
  1044. * Destroy the connection. Similar to [`.close`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.close()),
  1045. * but also removes the connection from Mongoose's `connections` list and prevents the
  1046. * connection from ever being re-opened.
  1047. *
  1048. * @param {Boolean} [force]
  1049. * @returns {Promise}
  1050. */
  1051. Connection.prototype.destroy = async function destroy(force) {
  1052. if (typeof force === 'function' || (arguments.length === 2 && typeof arguments[1] === 'function')) {
  1053. throw new MongooseError('Connection.prototype.destroy() no longer accepts a callback');
  1054. }
  1055. if (force != null && typeof force === 'object') {
  1056. this.$wasForceClosed = !!force.force;
  1057. } else {
  1058. this.$wasForceClosed = !!force;
  1059. }
  1060. return this._close(force, true);
  1061. };
  1062. /**
  1063. * Closes the connection
  1064. *
  1065. * @param {Boolean} [force] optional
  1066. * @return {Promise}
  1067. * @api public
  1068. */
  1069. Connection.prototype.close = async function close(force) {
  1070. if (typeof force === 'function' || (arguments.length === 2 && typeof arguments[1] === 'function')) {
  1071. throw new MongooseError('Connection.prototype.close() no longer accepts a callback');
  1072. }
  1073. if (force != null && typeof force === 'object') {
  1074. this.$wasForceClosed = !!force.force;
  1075. } else {
  1076. this.$wasForceClosed = !!force;
  1077. }
  1078. if (this._lastHeartbeatAt != null) {
  1079. this._lastHeartbeatAt = null;
  1080. }
  1081. for (const model of Object.values(this.models)) {
  1082. // If manually disconnecting, make sure to clear each model's `$init`
  1083. // promise, so Mongoose knows to re-run `init()` in case the
  1084. // connection is re-opened. See gh-12047.
  1085. delete model.$init;
  1086. }
  1087. return this._close(force, false);
  1088. };
  1089. /**
  1090. * Handles closing the connection
  1091. *
  1092. * @param {Boolean} force
  1093. * @param {Boolean} destroy
  1094. * @returns {Connection} this
  1095. * @api private
  1096. */
  1097. Connection.prototype._close = async function _close(force, destroy) {
  1098. const _this = this;
  1099. const closeCalled = this._closeCalled;
  1100. this._closeCalled = true;
  1101. this._destroyCalled = destroy;
  1102. if (this.client != null) {
  1103. this.client._closeCalled = true;
  1104. this.client._destroyCalled = destroy;
  1105. }
  1106. const conn = this;
  1107. switch (this.readyState) {
  1108. case STATES.disconnected:
  1109. if (destroy) {
  1110. const index = this.base.connections.indexOf(conn);
  1111. if (index !== -1) {
  1112. this.base.connections.splice(index, 1);
  1113. }
  1114. }
  1115. if (!closeCalled) {
  1116. await this.doClose(force);
  1117. this.onClose(force);
  1118. }
  1119. break;
  1120. case STATES.connected:
  1121. this.readyState = STATES.disconnecting;
  1122. await this.doClose(force);
  1123. if (destroy) {
  1124. const index = _this.base.connections.indexOf(conn);
  1125. if (index !== -1) {
  1126. this.base.connections.splice(index, 1);
  1127. }
  1128. }
  1129. this.onClose(force);
  1130. break;
  1131. case STATES.connecting:
  1132. return new Promise((resolve, reject) => {
  1133. const _rerunClose = () => {
  1134. this.removeListener('open', _rerunClose);
  1135. this.removeListener('error', _rerunClose);
  1136. if (destroy) {
  1137. this.destroy(force).then(resolve, reject);
  1138. } else {
  1139. this.close(force).then(resolve, reject);
  1140. }
  1141. };
  1142. this.once('open', _rerunClose);
  1143. this.once('error', _rerunClose);
  1144. });
  1145. case STATES.disconnecting:
  1146. return new Promise(resolve => {
  1147. this.once('close', () => {
  1148. if (destroy && this.base.connections.indexOf(conn) !== -1) {
  1149. this.base.connections.splice(this.base.connections.indexOf(conn), 1);
  1150. }
  1151. resolve();
  1152. });
  1153. });
  1154. }
  1155. return this;
  1156. };
  1157. /**
  1158. * Abstract method that drivers must implement.
  1159. *
  1160. * @api private
  1161. */
  1162. Connection.prototype.doClose = function doClose() {
  1163. throw new Error('Connection#doClose unimplemented by driver');
  1164. };
  1165. /**
  1166. * Called when the connection closes
  1167. *
  1168. * @emits "close"
  1169. * @api private
  1170. */
  1171. Connection.prototype.onClose = function onClose(force) {
  1172. this.readyState = STATES.disconnected;
  1173. // avoid having the collection subscribe to our event emitter
  1174. // to prevent 0.3 warning
  1175. for (const i in this.collections) {
  1176. if (Object.hasOwn(this.collections, i)) {
  1177. this.collections[i].onClose(force);
  1178. }
  1179. }
  1180. this.emit('close', force);
  1181. const wasForceClosed = typeof force === 'object' && force !== null ? force.force : force;
  1182. for (const db of this.otherDbs) {
  1183. this._destroyCalled ? db.destroy({ force: wasForceClosed, skipCloseClient: true }) : db.close({ force: wasForceClosed, skipCloseClient: true });
  1184. }
  1185. };
  1186. /**
  1187. * Retrieves a raw collection instance, creating it if not cached.
  1188. * This method returns a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)).
  1189. * Using a Collection bypasses Mongoose middleware, validation, and casting,
  1190. * letting you use [MongoDB Node.js driver](https://mongodb.github.io/node-mongodb-native/) functionality directly.
  1191. *
  1192. * @param {String} name of the collection
  1193. * @param {Object} [options] optional collection options
  1194. * @return {Collection} collection instance
  1195. * @api public
  1196. */
  1197. Connection.prototype.collection = function(name, options) {
  1198. const defaultOptions = {
  1199. autoIndex: this.config.autoIndex ?? this.base.options.autoIndex,
  1200. autoCreate: this.config.autoCreate ?? this.base.options.autoCreate,
  1201. autoSearchIndex: this.config.autoSearchIndex ?? this.base.options.autoSearchIndex
  1202. };
  1203. options = Object.assign({}, defaultOptions, options ? clone(options) : {});
  1204. options.$wasForceClosed = this.$wasForceClosed;
  1205. const Collection = this.base?.__driver?.Collection || driver.get().Collection;
  1206. if (!(name in this.collections)) {
  1207. this.collections[name] = new Collection(name, this, options);
  1208. }
  1209. return this.collections[name];
  1210. };
  1211. /**
  1212. * Declares a plugin executed on all schemas you pass to `conn.model()`
  1213. *
  1214. * Equivalent to calling `.plugin(fn)` on each schema you create.
  1215. *
  1216. * #### Example:
  1217. *
  1218. * const db = mongoose.createConnection('mongodb://127.0.0.1:27017/mydb');
  1219. * db.plugin(() => console.log('Applied'));
  1220. * db.plugins.length; // 1
  1221. *
  1222. * db.model('Test', new Schema({})); // Prints "Applied"
  1223. *
  1224. * @param {Function} fn plugin callback
  1225. * @param {Object} [opts] optional options
  1226. * @return {Connection} this
  1227. * @see plugins https://mongoosejs.com/docs/plugins.html
  1228. * @api public
  1229. */
  1230. Connection.prototype.plugin = function(fn, opts) {
  1231. this.plugins.push([fn, opts]);
  1232. return this;
  1233. };
  1234. /**
  1235. * Defines or retrieves a model.
  1236. *
  1237. * const mongoose = require('mongoose');
  1238. * const db = mongoose.createConnection(..);
  1239. * db.model('Venue', new Schema(..));
  1240. * const Ticket = db.model('Ticket', new Schema(..));
  1241. * const Venue = db.model('Venue');
  1242. *
  1243. * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the `utils.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._
  1244. *
  1245. * #### Example:
  1246. *
  1247. * const schema = new Schema({ name: String }, { collection: 'actor' });
  1248. *
  1249. * // or
  1250. *
  1251. * schema.set('collection', 'actor');
  1252. *
  1253. * // or
  1254. *
  1255. * const collectionName = 'actor'
  1256. * const M = conn.model('Actor', schema, collectionName)
  1257. *
  1258. * @param {String|Function} name the model name or class extending Model
  1259. * @param {Schema} [schema] a schema. necessary when defining a model
  1260. * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name
  1261. * @param {Object} [options]
  1262. * @param {Boolean} [options.overwriteModels=false] If true, overwrite existing models with the same name to avoid `OverwriteModelError`
  1263. * @see Mongoose#model https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()
  1264. * @return {Model} The compiled model
  1265. * @api public
  1266. */
  1267. Connection.prototype.model = function model(name, schema, collection, options) {
  1268. if (!(this instanceof Connection)) {
  1269. throw new MongooseError('`connection.model()` should not be run with ' +
  1270. '`new`. If you are doing `new db.model(foo)(bar)`, use ' +
  1271. '`db.model(foo)(bar)` instead');
  1272. }
  1273. let fn;
  1274. if (typeof name === 'function') {
  1275. fn = name;
  1276. name = fn.name;
  1277. }
  1278. // collection name discovery
  1279. if (typeof schema === 'string') {
  1280. collection = schema;
  1281. schema = false;
  1282. }
  1283. if (utils.isObject(schema)) {
  1284. if (!schema.instanceOfSchema) {
  1285. schema = new Schema(schema);
  1286. } else if (!(schema instanceof this.base.Schema)) {
  1287. schema = schema._clone(this.base.Schema);
  1288. }
  1289. }
  1290. if (schema && !schema.instanceOfSchema) {
  1291. throw new Error('The 2nd parameter to `mongoose.model()` should be a ' +
  1292. 'schema or a POJO');
  1293. }
  1294. const defaultOptions = { cache: false, overwriteModels: this.base.options.overwriteModels };
  1295. const opts = Object.assign(defaultOptions, options, { connection: this });
  1296. if (this.models[name] && !collection && opts.overwriteModels !== true) {
  1297. // model exists but we are not subclassing with custom collection
  1298. if (schema?.instanceOfSchema && schema !== this.models[name].schema) {
  1299. throw new MongooseError.OverwriteModelError(name);
  1300. }
  1301. return this.models[name];
  1302. }
  1303. let model;
  1304. if (schema?.instanceOfSchema) {
  1305. applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied');
  1306. // compile a model
  1307. model = this.base._model(fn || name, schema, collection, opts);
  1308. // only the first model with this name is cached to allow
  1309. // for one-offs with custom collection names etc.
  1310. if (!this.models[name]) {
  1311. this.models[name] = model;
  1312. }
  1313. // Errors handled internally, so safe to ignore error
  1314. model.init().catch(function $modelInitNoop() {});
  1315. return model;
  1316. }
  1317. if (this.models[name] && collection) {
  1318. // subclassing current model with alternate collection
  1319. model = this.models[name];
  1320. schema = model.prototype.schema;
  1321. const sub = model.__subclass(this, schema, collection);
  1322. // do not cache the sub model
  1323. return sub;
  1324. }
  1325. if (arguments.length === 1) {
  1326. model = this.models[name];
  1327. if (!model) {
  1328. throw new MongooseError.MissingSchemaError(name);
  1329. }
  1330. return model;
  1331. }
  1332. if (!model) {
  1333. throw new MongooseError.MissingSchemaError(name);
  1334. }
  1335. if (this === model.prototype.db
  1336. && (!collection || collection === model.collection.name)) {
  1337. // model already uses this connection.
  1338. // only the first model with this name is cached to allow
  1339. // for one-offs with custom collection names etc.
  1340. if (!this.models[name]) {
  1341. this.models[name] = model;
  1342. }
  1343. return model;
  1344. }
  1345. this.models[name] = model.__subclass(this, schema, collection);
  1346. return this.models[name];
  1347. };
  1348. /**
  1349. * Removes the model named `name` from this connection, if it exists. You can
  1350. * use this function to clean up any models you created in your tests to
  1351. * prevent OverwriteModelErrors.
  1352. *
  1353. * #### Example:
  1354. *
  1355. * conn.model('User', new Schema({ name: String }));
  1356. * console.log(conn.model('User')); // Model object
  1357. * conn.deleteModel('User');
  1358. * console.log(conn.model('User')); // undefined
  1359. *
  1360. * // Usually useful in a Mocha `afterEach()` hook
  1361. * afterEach(function() {
  1362. * conn.deleteModel(/.+/); // Delete every model
  1363. * });
  1364. *
  1365. * @api public
  1366. * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp.
  1367. * @return {Connection} this
  1368. */
  1369. Connection.prototype.deleteModel = function deleteModel(name) {
  1370. if (typeof name === 'string') {
  1371. const model = this.model(name);
  1372. if (model == null) {
  1373. return this;
  1374. }
  1375. const collectionName = model.collection.name;
  1376. delete this.models[name];
  1377. delete this.collections[collectionName];
  1378. this.emit('deleteModel', model);
  1379. } else if (name instanceof RegExp) {
  1380. const pattern = name;
  1381. const names = this.modelNames();
  1382. for (const name of names) {
  1383. if (pattern.test(name)) {
  1384. this.deleteModel(name);
  1385. }
  1386. }
  1387. } else {
  1388. throw new Error('First parameter to `deleteModel()` must be a string ' +
  1389. 'or regexp, got "' + name + '"');
  1390. }
  1391. return this;
  1392. };
  1393. /**
  1394. * Watches the entire underlying database for changes. Similar to
  1395. * [`Model.watch()`](https://mongoosejs.com/docs/api/model.html#Model.watch()).
  1396. *
  1397. * This function does **not** trigger any middleware. In particular, it
  1398. * does **not** trigger aggregate middleware.
  1399. *
  1400. * The ChangeStream object is an event emitter that emits the following events:
  1401. *
  1402. * - 'change': A change occurred, see below example
  1403. * - '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.
  1404. * - 'end': Emitted if the underlying stream is closed
  1405. * - 'close': Emitted if the underlying stream is closed
  1406. *
  1407. * #### Example:
  1408. *
  1409. * const User = conn.model('User', new Schema({ name: String }));
  1410. *
  1411. * const changeStream = conn.watch().on('change', data => console.log(data));
  1412. *
  1413. * // Triggers a 'change' event on the change stream.
  1414. * await User.create({ name: 'test' });
  1415. *
  1416. * @api public
  1417. * @param {Array} [pipeline]
  1418. * @param {Object} [options] passed without changes to [the MongoDB driver's `Db#watch()` function](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#watch)
  1419. * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
  1420. */
  1421. Connection.prototype.watch = function watch(pipeline, options) {
  1422. const changeStreamThunk = cb => {
  1423. immediate(() => {
  1424. if (this.readyState === STATES.connecting) {
  1425. this.once('open', function() {
  1426. const driverChangeStream = this.db.watch(pipeline, options);
  1427. cb(null, driverChangeStream);
  1428. });
  1429. } else {
  1430. const driverChangeStream = this.db.watch(pipeline, options);
  1431. cb(null, driverChangeStream);
  1432. }
  1433. });
  1434. };
  1435. const changeStream = new ChangeStream(changeStreamThunk, pipeline, options);
  1436. return changeStream;
  1437. };
  1438. /**
  1439. * Returns a promise that resolves when this connection
  1440. * successfully connects to MongoDB, or rejects if this connection failed
  1441. * to connect.
  1442. *
  1443. * #### Example:
  1444. *
  1445. * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/test').
  1446. * asPromise();
  1447. * conn.readyState; // 1, means Mongoose is connected
  1448. *
  1449. * @api public
  1450. * @return {Promise}
  1451. */
  1452. Connection.prototype.asPromise = async function asPromise() {
  1453. try {
  1454. await this.$initialConnection;
  1455. return this;
  1456. } catch (err) {
  1457. throw _handleConnectionErrors(err);
  1458. }
  1459. };
  1460. /**
  1461. * Returns an array of model names created on this connection.
  1462. * @api public
  1463. * @return {String[]}
  1464. */
  1465. Connection.prototype.modelNames = function modelNames() {
  1466. return Object.keys(this.models);
  1467. };
  1468. /**
  1469. * Returns if the connection requires authentication after it is opened. Generally if a
  1470. * username and password are both provided than authentication is needed, but in some cases a
  1471. * password is not required.
  1472. *
  1473. * @api private
  1474. * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false.
  1475. */
  1476. Connection.prototype.shouldAuthenticate = function shouldAuthenticate() {
  1477. return this.user != null &&
  1478. (this.pass != null || this.authMechanismDoesNotRequirePassword());
  1479. };
  1480. /**
  1481. * Returns a boolean value that specifies if the current authentication mechanism needs a
  1482. * password to authenticate according to the auth objects passed into the openUri methods.
  1483. *
  1484. * @api private
  1485. * @return {Boolean} true if the authentication mechanism specified in the options object requires
  1486. * a password, otherwise false.
  1487. */
  1488. Connection.prototype.authMechanismDoesNotRequirePassword = function authMechanismDoesNotRequirePassword() {
  1489. if (this.options?.auth) {
  1490. return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0;
  1491. }
  1492. return true;
  1493. };
  1494. /**
  1495. * Returns a boolean value that specifies if the provided objects object provides enough
  1496. * data to authenticate with. Generally this is true if the username and password are both specified
  1497. * but in some authentication methods, a password is not required for authentication so only a username
  1498. * is required.
  1499. *
  1500. * @param {Object} [options] the options object passed into the openUri methods.
  1501. * @api private
  1502. * @return {Boolean} true if the provided options object provides enough data to authenticate with,
  1503. * otherwise false.
  1504. */
  1505. Connection.prototype.optionsProvideAuthenticationData = function optionsProvideAuthenticationData(options) {
  1506. return (options) &&
  1507. (options.user) &&
  1508. ((options.pass) || this.authMechanismDoesNotRequirePassword());
  1509. };
  1510. /**
  1511. * Returns the [MongoDB driver `MongoClient`](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html) instance
  1512. * that this connection uses to talk to MongoDB.
  1513. *
  1514. * #### Example:
  1515. *
  1516. * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/test').
  1517. * asPromise();
  1518. *
  1519. * conn.getClient(); // MongoClient { ... }
  1520. *
  1521. * @api public
  1522. * @return {MongoClient}
  1523. */
  1524. Connection.prototype.getClient = function getClient() {
  1525. return this.client;
  1526. };
  1527. /**
  1528. * Set the [MongoDB driver `MongoClient`](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html) instance
  1529. * that this connection uses to talk to MongoDB. This is useful if you already have a MongoClient instance, and want to
  1530. * reuse it.
  1531. *
  1532. * #### Example:
  1533. *
  1534. * const client = await mongodb.MongoClient.connect('mongodb://127.0.0.1:27017/test');
  1535. *
  1536. * const conn = mongoose.createConnection().setClient(client);
  1537. *
  1538. * conn.getClient(); // MongoClient { ... }
  1539. * conn.readyState; // 1, means 'CONNECTED'
  1540. *
  1541. * @api public
  1542. * @param {MongClient} client The Client to set to be used.
  1543. * @return {Connection} this
  1544. */
  1545. Connection.prototype.setClient = function setClient() {
  1546. throw new MongooseError('Connection#setClient not implemented by driver');
  1547. };
  1548. /*!
  1549. * Called internally by `openUri()` to create a MongoClient instance.
  1550. */
  1551. Connection.prototype.createClient = function createClient() {
  1552. throw new MongooseError('Connection#createClient not implemented by driver');
  1553. };
  1554. /**
  1555. * Syncs all the indexes for the models registered with this connection.
  1556. *
  1557. * @param {Object} [options]
  1558. * @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.
  1559. * @return {Promise<Object>} Returns a Promise, when the Promise resolves the value is a list of the dropped indexes.
  1560. */
  1561. Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
  1562. const result = {};
  1563. const errorsMap = { };
  1564. const { continueOnError } = options;
  1565. delete options.continueOnError;
  1566. for (const model of Object.values(this.models)) {
  1567. try {
  1568. result[model.modelName] = await model.syncIndexes(options);
  1569. } catch (err) {
  1570. if (!continueOnError) {
  1571. errorsMap[model.modelName] = err;
  1572. break;
  1573. } else {
  1574. result[model.modelName] = err;
  1575. }
  1576. }
  1577. }
  1578. if (!continueOnError && utils.hasOwnKeys(errorsMap)) {
  1579. const message = Object.entries(errorsMap).map(([modelName, err]) => `${modelName}: ${err.message}`).join(', ');
  1580. const syncIndexesError = new SyncIndexesError(message, errorsMap);
  1581. throw syncIndexesError;
  1582. }
  1583. return result;
  1584. };
  1585. /**
  1586. * Switches to a different database using the same [connection pool](https://mongoosejs.com/docs/connections.html#connection_pools).
  1587. *
  1588. * Returns a new connection object, with the new db.
  1589. *
  1590. * #### Example:
  1591. *
  1592. * // Connect to `initialdb` first
  1593. * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/initialdb').asPromise();
  1594. *
  1595. * // Creates an un-cached connection to `mydb`
  1596. * const db = conn.useDb('mydb');
  1597. * // Creates a cached connection to `mydb2`. All calls to `conn.useDb('mydb2', { useCache: true })` will return the same
  1598. * // connection instance as opposed to creating a new connection instance
  1599. * const db2 = conn.useDb('mydb2', { useCache: true });
  1600. *
  1601. * @method useDb
  1602. * @memberOf Connection
  1603. * @param {String} name The database name
  1604. * @param {Object} [options]
  1605. * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object.
  1606. * @return {Connection} New Connection Object
  1607. * @api public
  1608. */
  1609. /**
  1610. * Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
  1611. *
  1612. * @method aggregate
  1613. * @memberOf Connection
  1614. * @param {Array} pipeline
  1615. * @param {Object} [options]
  1616. * @param {Boolean} [options.cursor=false] If true, make the Aggregate resolve to a Mongoose AggregationCursor rather than an array
  1617. * @return {Aggregate} Aggregation wrapper
  1618. * @api public
  1619. */
  1620. /**
  1621. * Removes the database connection with the given name created with with `useDb()`.
  1622. *
  1623. * Throws an error if the database connection was not found.
  1624. *
  1625. * #### Example:
  1626. *
  1627. * // Connect to `initialdb` first
  1628. * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/initialdb').asPromise();
  1629. *
  1630. * // Creates an un-cached connection to `mydb`
  1631. * const db = conn.useDb('mydb');
  1632. *
  1633. * // Closes `db`, and removes `db` from `conn.relatedDbs` and `conn.otherDbs`
  1634. * await conn.removeDb('mydb');
  1635. *
  1636. * @method removeDb
  1637. * @memberOf Connection
  1638. * @param {String} name The database name
  1639. * @return {Connection} this
  1640. * @api public
  1641. */
  1642. /*!
  1643. * Module exports.
  1644. */
  1645. Connection.STATES = STATES;
  1646. module.exports = Connection;