diff options
Diffstat (limited to 'node_modules/mongoose/lib/connection.js')
-rw-r--r-- | node_modules/mongoose/lib/connection.js | 1459 |
1 files changed, 0 insertions, 1459 deletions
diff --git a/node_modules/mongoose/lib/connection.js b/node_modules/mongoose/lib/connection.js deleted file mode 100644 index 2d2e7fd..0000000 --- a/node_modules/mongoose/lib/connection.js +++ /dev/null @@ -1,1459 +0,0 @@ -'use strict'; - -/*! - * Module dependencies. - */ - -const ChangeStream = require('./cursor/ChangeStream'); -const EventEmitter = require('events').EventEmitter; -const Schema = require('./schema'); -const Collection = require('./driver').get().Collection; -const STATES = require('./connectionstate'); -const MongooseError = require('./error/index'); -const PromiseProvider = require('./promise_provider'); -const ServerSelectionError = require('./error/serverSelection'); -const applyPlugins = require('./helpers/schema/applyPlugins'); -const promiseOrCallback = require('./helpers/promiseOrCallback'); -const get = require('./helpers/get'); -const immediate = require('./helpers/immediate'); -const mongodb = require('mongodb'); -const pkg = require('../package.json'); -const utils = require('./utils'); - -const parseConnectionString = require('mongodb/lib/core').parseConnectionString; - -const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol; -const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments; - -let id = 0; - -/*! - * A list of authentication mechanisms that don't require a password for authentication. - * This is used by the authMechanismDoesNotRequirePassword method. - * - * @api private - */ -const noPasswordAuthMechanisms = [ - 'MONGODB-X509' -]; - -/** - * Connection constructor - * - * For practical reasons, a Connection equals a Db. - * - * @param {Mongoose} base a mongoose instance - * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter - * @event `connecting`: Emitted when `connection.openUri()` is executed on this connection. - * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios. - * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models. - * @event `disconnecting`: Emitted when `connection.close()` was executed. - * @event `disconnected`: Emitted after getting disconnected from the db. - * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models. - * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successful connection. - * @event `error`: Emitted when an error occurs on this connection. - * @event `fullsetup`: Emitted after the driver has connected to primary and all secondaries if specified in the connection string. - * @api public - */ - -function Connection(base) { - this.base = base; - this.collections = {}; - this.models = {}; - this.config = { autoIndex: true }; - this.replica = false; - this.options = null; - this.otherDbs = []; // FIXME: To be replaced with relatedDbs - this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection - this.states = STATES; - this._readyState = STATES.disconnected; - this._closeCalled = false; - this._hasOpened = false; - this.plugins = []; - this.id = id++; -} - -/*! - * Inherit from EventEmitter - */ - -Connection.prototype.__proto__ = EventEmitter.prototype; - -/** - * Connection ready state - * - * - 0 = disconnected - * - 1 = connected - * - 2 = connecting - * - 3 = disconnecting - * - * Each state change emits its associated event name. - * - * ####Example - * - * conn.on('connected', callback); - * conn.on('disconnected', callback); - * - * @property readyState - * @memberOf Connection - * @instance - * @api public - */ - -Object.defineProperty(Connection.prototype, 'readyState', { - get: function() { - return this._readyState; - }, - set: function(val) { - if (!(val in STATES)) { - throw new Error('Invalid connection state: ' + val); - } - - if (this._readyState !== val) { - this._readyState = val; - // [legacy] loop over the otherDbs on this connection and change their state - for (const db of this.otherDbs) { - db.readyState = val; - } - - // loop over relatedDbs on this connection and change their state - for (const k in this.relatedDbs) { - this.relatedDbs[k].readyState = val; - } - - if (STATES.connected === val) { - this._hasOpened = true; - } - - this.emit(STATES[val]); - } - } -}); - -/** - * Gets the value of the option `key`. Equivalent to `conn.options[key]` - * - * ####Example: - * - * conn.get('test'); // returns the 'test' value - * - * @param {String} key - * @method get - * @api public - */ - -Connection.prototype.get = function(key) { - return get(this.options, key); -}; - -/** - * Sets the value of the option `key`. Equivalent to `conn.options[key] = val` - * - * Supported options include: - * - * - `maxTimeMS`: Set [`maxTimeMS`](/docs/api.html#query_Query-maxTimeMS) for all queries on this connection. - * - `useFindAndModify`: Set to `false` to work around the [`findAndModify()` deprecation warning](/docs/deprecations.html#findandmodify) - * - * ####Example: - * - * conn.set('test', 'foo'); - * conn.get('test'); // 'foo' - * conn.options.test; // 'foo' - * - * @param {String} key - * @param {Any} val - * @method set - * @api public - */ - -Connection.prototype.set = function(key, val) { - this.options = this.options || {}; - this.options[key] = val; - return val; -}; - -/** - * A hash of the collections associated with this connection - * - * @property collections - * @memberOf Connection - * @instance - * @api public - */ - -Connection.prototype.collections; - -/** - * The name of the database this connection points to. - * - * ####Example - * - * mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb" - * - * @property name - * @memberOf Connection - * @instance - * @api public - */ - -Connection.prototype.name; - -/** - * A [POJO](https://masteringjs.io/tutorials/fundamentals/pojo) containing - * a map from model names to models. Contains all models that have been - * added to this connection using [`Connection#model()`](/docs/api/connection.html#connection_Connection-model). - * - * ####Example - * - * const conn = mongoose.createConnection(); - * const Test = conn.model('Test', mongoose.Schema({ name: String })); - * - * Object.keys(conn.models).length; // 1 - * conn.models.Test === Test; // true - * - * @property models - * @memberOf Connection - * @instance - * @api public - */ - -Connection.prototype.models; - -/** - * A number identifier for this connection. Used for debugging when - * you have [multiple connections](/docs/connections.html#multiple_connections). - * - * ####Example - * - * // The default connection has `id = 0` - * mongoose.connection.id; // 0 - * - * // If you create a new connection, Mongoose increments id - * const conn = mongoose.createConnection(); - * conn.id; // 1 - * - * @property id - * @memberOf Connection - * @instance - * @api public - */ - -Connection.prototype.id; - -/** - * The plugins that will be applied to all models created on this connection. - * - * ####Example: - * - * const db = mongoose.createConnection('mongodb://localhost:27017/mydb'); - * db.plugin(() => console.log('Applied')); - * db.plugins.length; // 1 - * - * db.model('Test', new Schema({})); // Prints "Applied" - * - * @property plugins - * @memberOf Connection - * @instance - * @api public - */ - -Object.defineProperty(Connection.prototype, 'plugins', { - configurable: false, - enumerable: true, - writable: true -}); - -/** - * The host name portion of the URI. If multiple hosts, such as a replica set, - * this will contain the first host name in the URI - * - * ####Example - * - * mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost" - * - * @property host - * @memberOf Connection - * @instance - * @api public - */ - -Object.defineProperty(Connection.prototype, 'host', { - configurable: true, - enumerable: true, - writable: true -}); - -/** - * The port portion of the URI. If multiple hosts, such as a replica set, - * this will contain the port from the first host name in the URI. - * - * ####Example - * - * mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017 - * - * @property port - * @memberOf Connection - * @instance - * @api public - */ - -Object.defineProperty(Connection.prototype, 'port', { - configurable: true, - enumerable: true, - writable: true -}); - -/** - * The username specified in the URI - * - * ####Example - * - * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val" - * - * @property user - * @memberOf Connection - * @instance - * @api public - */ - -Object.defineProperty(Connection.prototype, 'user', { - configurable: true, - enumerable: true, - writable: true -}); - -/** - * The password specified in the URI - * - * ####Example - * - * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw" - * - * @property pass - * @memberOf Connection - * @instance - * @api public - */ - -Object.defineProperty(Connection.prototype, 'pass', { - configurable: true, - enumerable: true, - writable: true -}); - -/** - * The mongodb.Db instance, set when the connection is opened - * - * @property db - * @memberOf Connection - * @instance - * @api public - */ - -Connection.prototype.db; - -/** - * A hash of the global options that are associated with this connection - * - * @property config - * @memberOf Connection - * @instance - * @api public - */ - -Connection.prototype.config; - -/** - * Helper for `createCollection()`. Will explicitly create the given collection - * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/) - * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose. - * - * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection) - * - * @method createCollection - * @param {string} collection The collection to create - * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection) - * @param {Function} [callback] - * @return {Promise} - * @api public - */ - -Connection.prototype.createCollection = _wrapConnHelper(function createCollection(collection, options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; - } - this.db.createCollection(collection, options, cb); -}); - -/** - * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions) - * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/), - * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). - * - * ####Example: - * - * const session = await conn.startSession(); - * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session }); - * await doc.remove(); - * // `doc` will always be null, even if reading from a replica set - * // secondary. Without causal consistency, it is possible to - * // get a doc back from the below query if the query reads from a - * // secondary that is experiencing replication lag. - * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' }); - * - * - * @method startSession - * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession) - * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency - * @param {Function} [callback] - * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession` - * @api public - */ - -Connection.prototype.startSession = _wrapConnHelper(function startSession(options, cb) { - if (typeof options === 'function') { - cb = options; - options = null; - } - const session = this.client.startSession(options); - cb(null, session); -}); - -/** - * _Requires MongoDB >= 3.6.0._ Executes the wrapped async function - * in a transaction. Mongoose will commit the transaction if the - * async function executes successfully and attempt to retry if - * there was a retriable error. - * - * Calls the MongoDB driver's [`session.withTransaction()`](http://mongodb.github.io/node-mongodb-native/3.5/api/ClientSession.html#withTransaction), - * but also handles resetting Mongoose document state as shown below. - * - * ####Example: - * - * const doc = new Person({ name: 'Will Riker' }); - * await db.transaction(async function setRank(session) { - * doc.rank = 'Captain'; - * await doc.save({ session }); - * doc.isNew; // false - * - * // Throw an error to abort the transaction - * throw new Error('Oops!'); - * }).catch(() => {}); - * - * // true, `transaction()` reset the document's state because the - * // transaction was aborted. - * doc.isNew; - * - * @method transaction - * @param {Function} fn Function to execute in a transaction - * @return {Promise<Any>} promise that resolves to the returned value of `fn` - * @api public - */ - -Connection.prototype.transaction = function transaction(fn) { - return this.startSession().then(session => { - session[sessionNewDocuments] = new Map(); - return session.withTransaction(() => fn(session)). - then(res => { - delete session[sessionNewDocuments]; - return res; - }). - catch(err => { - // If transaction was aborted, we need to reset newly - // inserted documents' `isNew`. - for (const doc of session[sessionNewDocuments].keys()) { - const state = session[sessionNewDocuments].get(doc); - if (state.hasOwnProperty('isNew')) { - doc.isNew = state.isNew; - } - if (state.hasOwnProperty('versionKey')) { - doc.set(doc.schema.options.versionKey, state.versionKey); - } - - for (const path of state.modifiedPaths) { - doc.$__.activePaths.paths[path] = 'modify'; - doc.$__.activePaths.states.modify[path] = true; - } - - for (const path of state.atomics.keys()) { - const val = doc.$__getValue(path); - if (val == null) { - continue; - } - val[arrayAtomicsSymbol] = state.atomics.get(path); - } - } - delete session[sessionNewDocuments]; - throw err; - }); - }); -}; - -/** - * Helper for `dropCollection()`. Will delete the given collection, including - * all documents and indexes. - * - * @method dropCollection - * @param {string} collection The collection to delete - * @param {Function} [callback] - * @return {Promise} - * @api public - */ - -Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(collection, cb) { - this.db.dropCollection(collection, cb); -}); - -/** - * Helper for `dropDatabase()`. Deletes the given database, including all - * collections, documents, and indexes. - * - * ####Example: - * - * const conn = mongoose.createConnection('mongodb://localhost:27017/mydb'); - * // Deletes the entire 'mydb' database - * await conn.dropDatabase(); - * - * @method dropDatabase - * @param {Function} [callback] - * @return {Promise} - * @api public - */ - -Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) { - // If `dropDatabase()` is called, this model's collection will not be - // init-ed. It is sufficiently common to call `dropDatabase()` after - // `mongoose.connect()` but before creating models that we want to - // support this. See gh-6967 - for (const name of Object.keys(this.models)) { - delete this.models[name].$init; - } - this.db.dropDatabase(cb); -}); - -/*! - * ignore - */ - -function _wrapConnHelper(fn) { - return function() { - const cb = arguments.length > 0 ? arguments[arguments.length - 1] : null; - const argsWithoutCb = typeof cb === 'function' ? - Array.prototype.slice.call(arguments, 0, arguments.length - 1) : - Array.prototype.slice.call(arguments); - const disconnectedError = new MongooseError('Connection ' + this.id + - ' was disconnected when calling `' + fn.name + '`'); - return promiseOrCallback(cb, cb => { - // Make it ok to call collection helpers before `mongoose.connect()` - // as long as `mongoose.connect()` is called on the same tick. - // Re: gh-8534 - immediate(() => { - if (this.readyState === STATES.connecting && this._shouldBufferCommands()) { - this.once('open', function() { - fn.apply(this, argsWithoutCb.concat([cb])); - }); - } else if (this.readyState === STATES.disconnected && this.db == null) { - cb(disconnectedError); - } else { - try { - fn.apply(this, argsWithoutCb.concat([cb])); - } catch (err) { - return cb(err); - } - } - }); - }); - }; -} - -/*! - * ignore - */ - -Connection.prototype._shouldBufferCommands = function _shouldBufferCommands() { - if (this.config.bufferCommands != null) { - return this.config.bufferCommands; - } - if (this.base != null && this.base.get('bufferCommands') != null) { - return this.base.get('bufferCommands'); - } - return true; -}; - -/** - * error - * - * Graceful error handling, passes error to callback - * if available, else emits error on the connection. - * - * @param {Error} err - * @param {Function} callback optional - * @api private - */ - -Connection.prototype.error = function(err, callback) { - if (callback) { - callback(err); - return null; - } - if (this.listeners('error').length > 0) { - this.emit('error', err); - } - return Promise.reject(err); -}; - -/** - * Called when the connection is opened - * - * @api private - */ - -Connection.prototype.onOpen = function() { - this.readyState = STATES.connected; - - // avoid having the collection subscribe to our event emitter - // to prevent 0.3 warning - for (const i in this.collections) { - if (utils.object.hasOwnProperty(this.collections, i)) { - this.collections[i].onOpen(); - } - } - - this.emit('open'); -}; - -/** - * Opens the connection with a URI using `MongoClient.connect()`. - * - * @param {String} uri The URI to connect with. - * @param {Object} [options] Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect - * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. - * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. - * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. - * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. - * @param {Number} [options.poolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). - * @param {Boolean} [options.useUnifiedTopology=false] False by default. Set to `true` to opt in to the MongoDB driver's replica set and sharded cluster monitoring engine. - * @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). - * @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. - * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. - * @param {Boolean} [options.useNewUrlParser=false] False by default. Set to `true` to opt in to the MongoDB driver's new URL parser logic. - * @param {Boolean} [options.useCreateIndex=true] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init). - * @param {Boolean} [options.useFindAndModify=true] True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. - * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. - * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above. - * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html). - * @param {Number} [options.bufferMaxEntries] This option does nothing if `useUnifiedTopology` is set. The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection. - * @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). - * @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. - * @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. - * @param {Function} [callback] - * @returns {Connection} this - * @api public - */ - -Connection.prototype.openUri = function(uri, options, callback) { - if (typeof options === 'function') { - callback = options; - options = null; - } - - if (['string', 'number'].indexOf(typeof options) !== -1) { - throw new MongooseError('Mongoose 5.x no longer supports ' + - '`mongoose.connect(host, dbname, port)` or ' + - '`mongoose.createConnection(host, dbname, port)`. See ' + - 'http://mongoosejs.com/docs/connections.html for supported connection syntax'); - } - - if (typeof uri !== 'string') { - throw new MongooseError('The `uri` parameter to `openUri()` must be a ' + - `string, got "${typeof uri}". Make sure the first parameter to ` + - '`mongoose.connect()` or `mongoose.createConnection()` is a string.'); - } - - if (callback != null && typeof callback !== 'function') { - throw new MongooseError('3rd parameter to `mongoose.connect()` or ' + - '`mongoose.createConnection()` must be a function, got "' + - typeof callback + '"'); - } - - if (this.readyState === STATES.connecting || this.readyState === STATES.connected) { - if (this._connectionString !== uri) { - throw new MongooseError('Can\'t call `openUri()` on an active connection with ' + - 'different connection strings. Make sure you aren\'t calling `mongoose.connect()` ' + - 'multiple times. See: https://mongoosejs.com/docs/connections.html#multiple_connections'); - } - - if (typeof callback === 'function') { - this.$initialConnection = this.$initialConnection.then( - () => callback(null, this), - err => callback(err) - ); - } - return this; - } - - this._connectionString = uri; - this.readyState = STATES.connecting; - this._closeCalled = false; - - const Promise = PromiseProvider.get(); - const _this = this; - - if (options) { - options = utils.clone(options); - const autoIndex = options.config && options.config.autoIndex != null ? - options.config.autoIndex : - options.autoIndex; - if (autoIndex != null) { - this.config.autoIndex = autoIndex !== false; - delete options.config; - delete options.autoIndex; - } - - if ('autoCreate' in options) { - this.config.autoCreate = !!options.autoCreate; - delete options.autoCreate; - } - if ('useCreateIndex' in options) { - this.config.useCreateIndex = !!options.useCreateIndex; - delete options.useCreateIndex; - } - - if ('useFindAndModify' in options) { - this.config.useFindAndModify = !!options.useFindAndModify; - delete options.useFindAndModify; - } - - // Backwards compat - if (options.user || options.pass) { - options.auth = options.auth || {}; - options.auth.user = options.user; - options.auth.password = options.pass; - - this.user = options.user; - this.pass = options.pass; - } - delete options.user; - delete options.pass; - - if (options.bufferCommands != null) { - if (options.bufferMaxEntries == null) { - options.bufferMaxEntries = 0; - } - this.config.bufferCommands = options.bufferCommands; - delete options.bufferCommands; - } - - if (options.useMongoClient != null) { - handleUseMongoClient(options); - } - } else { - options = {}; - } - - this._connectionOptions = options; - const dbName = options.dbName; - if (dbName != null) { - this.$dbName = dbName; - } - delete options.dbName; - - if (!('promiseLibrary' in options)) { - options.promiseLibrary = PromiseProvider.get(); - } - if (!('useNewUrlParser' in options)) { - if ('useNewUrlParser' in this.base.options) { - options.useNewUrlParser = this.base.options.useNewUrlParser; - } else { - options.useNewUrlParser = false; - } - } - if (!utils.hasUserDefinedProperty(options, 'useUnifiedTopology')) { - if (utils.hasUserDefinedProperty(this.base.options, 'useUnifiedTopology')) { - options.useUnifiedTopology = this.base.options.useUnifiedTopology; - } else { - options.useUnifiedTopology = false; - } - } - if (!utils.hasUserDefinedProperty(options, 'driverInfo')) { - options.driverInfo = { - name: 'Mongoose', - version: pkg.version - }; - } - - const parsePromise = new Promise((resolve, reject) => { - parseConnectionString(uri, options, (err, parsed) => { - if (err) { - return reject(err); - } - if (dbName) { - this.name = dbName; - } else if (parsed.defaultDatabase) { - this.name = parsed.defaultDatabase; - } else { - this.name = get(parsed, 'auth.db', null); - } - this.host = get(parsed, 'hosts.0.host', 'localhost'); - this.port = get(parsed, 'hosts.0.port', 27017); - this.user = this.user || get(parsed, 'auth.username'); - this.pass = this.pass || get(parsed, 'auth.password'); - resolve(); - }); - }); - - const promise = new Promise((resolve, reject) => { - const client = new mongodb.MongoClient(uri, options); - _this.client = client; - client.connect((error) => { - if (error) { - _this.readyState = STATES.disconnected; - return reject(error); - } - - _setClient(_this, client, options, dbName); - - resolve(_this); - }); - }); - - const serverSelectionError = new ServerSelectionError(); - this.$initialConnection = Promise.all([promise, parsePromise]). - then(res => res[0]). - catch(err => { - if (err != null && err.name === 'MongoServerSelectionError') { - err = serverSelectionError.assimilateError(err); - } - - if (this.listeners('error').length > 0) { - process.nextTick(() => this.emit('error', err)); - } - throw err; - }); - this.then = function(resolve, reject) { - return this.$initialConnection.then(() => { - if (typeof resolve === 'function') { - resolve(_this); - } - }, reject); - }; - this.catch = function(reject) { - return this.$initialConnection.catch(reject); - }; - - if (callback != null) { - this.$initialConnection = this.$initialConnection.then( - () => callback(null, this), - err => callback(err) - ); - } - - return this; -}; - -function _setClient(conn, client, options, dbName) { - const db = dbName != null ? client.db(dbName) : client.db(); - conn.db = db; - conn.client = client; - - const _handleReconnect = () => { - // If we aren't disconnected, we assume this reconnect is due to a - // socket timeout. If there's no activity on a socket for - // `socketTimeoutMS`, the driver will attempt to reconnect and emit - // this event. - if (conn.readyState !== STATES.connected) { - conn.readyState = STATES.connected; - conn.emit('reconnect'); - conn.emit('reconnected'); - conn.onOpen(); - } - }; - - // `useUnifiedTopology` events - const type = get(db, 's.topology.s.description.type', ''); - if (options.useUnifiedTopology) { - if (type === 'Single') { - const server = Array.from(db.s.topology.s.servers.values())[0]; - server.s.topology.on('serverHeartbeatSucceeded', () => { - _handleReconnect(); - }); - server.s.pool.on('reconnect', () => { - _handleReconnect(); - }); - client.on('serverDescriptionChanged', ev => { - const newDescription = ev.newDescription; - if (newDescription.type === 'Standalone') { - _handleReconnect(); - } else { - conn.readyState = STATES.disconnected; - } - }); - } else if (type.startsWith('ReplicaSet')) { - client.on('topologyDescriptionChanged', ev => { - // Emit disconnected if we've lost connectivity to _all_ servers - // in the replica set. - const description = ev.newDescription; - const servers = Array.from(ev.newDescription.servers.values()); - const allServersDisconnected = description.type === 'ReplicaSetNoPrimary' && - servers.reduce((cur, d) => cur || d.type === 'Unknown', false); - if (conn.readyState === STATES.connected && allServersDisconnected) { - // Implicitly emits 'disconnected' - conn.readyState = STATES.disconnected; - } else if (conn.readyState === STATES.disconnected && !allServersDisconnected) { - _handleReconnect(); - } - }); - - db.on('close', function() { - const type = get(db, 's.topology.s.description.type', ''); - if (type !== 'ReplicaSetWithPrimary') { - // Implicitly emits 'disconnected' - conn.readyState = STATES.disconnected; - } - }); - } - } - - // Backwards compat for mongoose 4.x - db.on('reconnect', function() { - _handleReconnect(); - }); - db.s.topology.on('reconnectFailed', function() { - conn.emit('reconnectFailed'); - }); - - if (!options.useUnifiedTopology) { - db.s.topology.on('left', function(data) { - conn.emit('left', data); - }); - } - db.s.topology.on('joined', function(data) { - conn.emit('joined', data); - }); - db.s.topology.on('fullsetup', function(data) { - conn.emit('fullsetup', data); - }); - if (get(db, 's.topology.s.coreTopology.s.pool') != null) { - db.s.topology.s.coreTopology.s.pool.on('attemptReconnect', function() { - conn.emit('attemptReconnect'); - }); - } - if (!options.useUnifiedTopology || !type.startsWith('ReplicaSet')) { - db.on('close', function() { - // Implicitly emits 'disconnected' - conn.readyState = STATES.disconnected; - }); - } - - if (!options.useUnifiedTopology) { - client.on('left', function() { - if (conn.readyState === STATES.connected && - get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') { - conn.readyState = STATES.disconnected; - } - }); - } - - db.on('timeout', function() { - conn.emit('timeout'); - }); - - delete conn.then; - delete conn.catch; - conn.readyState = STATES.connected; - - for (const i in conn.collections) { - if (utils.object.hasOwnProperty(conn.collections, i)) { - conn.collections[i].onOpen(); - } - } - - conn.emit('open'); -} - -/*! - * ignore - */ - -const handleUseMongoClient = function handleUseMongoClient(options) { - console.warn('WARNING: The `useMongoClient` option is no longer ' + - 'necessary in mongoose 5.x, please remove it.'); - const stack = new Error().stack; - console.warn(stack.substr(stack.indexOf('\n') + 1)); - delete options.useMongoClient; -}; - -/** - * Closes the connection - * - * @param {Boolean} [force] optional - * @param {Function} [callback] optional - * @return {Promise} - * @api public - */ - -Connection.prototype.close = function(force, callback) { - if (typeof force === 'function') { - callback = force; - force = false; - } - - this.$wasForceClosed = !!force; - - return promiseOrCallback(callback, cb => { - this._close(force, cb); - }); -}; - -/** - * Handles closing the connection - * - * @param {Boolean} force - * @param {Function} callback - * @api private - */ -Connection.prototype._close = function(force, callback) { - const _this = this; - this._closeCalled = true; - - switch (this.readyState) { - case STATES.disconnected: - callback(); - break; - - case STATES.connected: - this.readyState = STATES.disconnecting; - this.doClose(force, function(err) { - if (err) { - return callback(err); - } - _this.onClose(force); - callback(null); - }); - - break; - case STATES.connecting: - this.once('open', function() { - _this.close(callback); - }); - break; - - case STATES.disconnecting: - this.once('close', function() { - callback(); - }); - break; - } - - return this; -}; - -/** - * Called when the connection closes - * - * @api private - */ - -Connection.prototype.onClose = function(force) { - this.readyState = STATES.disconnected; - - // avoid having the collection subscribe to our event emitter - // to prevent 0.3 warning - for (const i in this.collections) { - if (utils.object.hasOwnProperty(this.collections, i)) { - this.collections[i].onClose(force); - } - } - - this.emit('close', force); -}; - -/** - * Retrieves a collection, creating it if not cached. - * - * Not typically needed by applications. Just talk to your collection through your model. - * - * @param {String} name of the collection - * @param {Object} [options] optional collection options - * @return {Collection} collection instance - * @api public - */ - -Connection.prototype.collection = function(name, options) { - options = options ? utils.clone(options) : {}; - options.$wasForceClosed = this.$wasForceClosed; - if (!(name in this.collections)) { - this.collections[name] = new Collection(name, this, options); - } - return this.collections[name]; -}; - -/** - * Declares a plugin executed on all schemas you pass to `conn.model()` - * - * Equivalent to calling `.plugin(fn)` on each schema you create. - * - * ####Example: - * const db = mongoose.createConnection('mongodb://localhost:27017/mydb'); - * db.plugin(() => console.log('Applied')); - * db.plugins.length; // 1 - * - * db.model('Test', new Schema({})); // Prints "Applied" - * - * @param {Function} fn plugin callback - * @param {Object} [opts] optional options - * @return {Connection} this - * @see plugins ./plugins.html - * @api public - */ - -Connection.prototype.plugin = function(fn, opts) { - this.plugins.push([fn, opts]); - return this; -}; - -/** - * Defines or retrieves a model. - * - * const mongoose = require('mongoose'); - * const db = mongoose.createConnection(..); - * db.model('Venue', new Schema(..)); - * const Ticket = db.model('Ticket', new Schema(..)); - * const Venue = db.model('Venue'); - * - * _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._ - * - * ####Example: - * - * const schema = new Schema({ name: String }, { collection: 'actor' }); - * - * // or - * - * schema.set('collection', 'actor'); - * - * // or - * - * const collectionName = 'actor' - * const M = conn.model('Actor', schema, collectionName) - * - * @param {String|Function} name the model name or class extending Model - * @param {Schema} [schema] a schema. necessary when defining a model - * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name - * @see Mongoose#model #index_Mongoose-model - * @return {Model} The compiled model - * @api public - */ - -Connection.prototype.model = function(name, schema, collection) { - if (!(this instanceof Connection)) { - throw new MongooseError('`connection.model()` should not be run with ' + - '`new`. If you are doing `new db.model(foo)(bar)`, use ' + - '`db.model(foo)(bar)` instead'); - } - - let fn; - if (typeof name === 'function') { - fn = name; - name = fn.name; - } - - // collection name discovery - if (typeof schema === 'string') { - collection = schema; - schema = false; - } - - if (utils.isObject(schema) && !schema.instanceOfSchema) { - schema = new Schema(schema); - } - if (schema && !schema.instanceOfSchema) { - throw new Error('The 2nd parameter to `mongoose.model()` should be a ' + - 'schema or a POJO'); - } - - if (this.models[name] && !collection) { - // model exists but we are not subclassing with custom collection - if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) { - throw new MongooseError.OverwriteModelError(name); - } - return this.models[name]; - } - - const opts = { cache: false, connection: this }; - let model; - - if (schema && schema.instanceOfSchema) { - applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied'); - - // compile a model - model = this.base.model(fn || name, schema, collection, opts); - - // only the first model with this name is cached to allow - // for one-offs with custom collection names etc. - if (!this.models[name]) { - this.models[name] = model; - } - - // Errors handled internally, so safe to ignore error - model.init(function $modelInitNoop() {}); - - return model; - } - - if (this.models[name] && collection) { - // subclassing current model with alternate collection - model = this.models[name]; - schema = model.prototype.schema; - const sub = model.__subclass(this, schema, collection); - // do not cache the sub model - return sub; - } - - // lookup model in mongoose module - model = this.base.models[name]; - - if (!model) { - throw new MongooseError.MissingSchemaError(name); - } - - if (this === model.prototype.db - && (!collection || collection === model.collection.name)) { - // model already uses this connection. - - // only the first model with this name is cached to allow - // for one-offs with custom collection names etc. - if (!this.models[name]) { - this.models[name] = model; - } - - return model; - } - this.models[name] = model.__subclass(this, schema, collection); - return this.models[name]; -}; - -/** - * Removes the model named `name` from this connection, if it exists. You can - * use this function to clean up any models you created in your tests to - * prevent OverwriteModelErrors. - * - * ####Example: - * - * conn.model('User', new Schema({ name: String })); - * console.log(conn.model('User')); // Model object - * conn.deleteModel('User'); - * console.log(conn.model('User')); // undefined - * - * // Usually useful in a Mocha `afterEach()` hook - * afterEach(function() { - * conn.deleteModel(/.+/); // Delete every model - * }); - * - * @api public - * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp. - * @return {Connection} this - */ - -Connection.prototype.deleteModel = function(name) { - if (typeof name === 'string') { - const model = this.model(name); - if (model == null) { - return this; - } - const collectionName = model.collection.name; - delete this.models[name]; - delete this.collections[collectionName]; - delete this.base.modelSchemas[name]; - } else if (name instanceof RegExp) { - const pattern = name; - const names = this.modelNames(); - for (const name of names) { - if (pattern.test(name)) { - this.deleteModel(name); - } - } - } else { - throw new Error('First parameter to `deleteModel()` must be a string ' + - 'or regexp, got "' + name + '"'); - } - - return this; -}; - -/** - * Watches the entire underlying database for changes. Similar to - * [`Model.watch()`](/docs/api/model.html#model_Model.watch). - * - * This function does **not** trigger any middleware. In particular, it - * does **not** trigger aggregate middleware. - * - * The ChangeStream object is an event emitter that emits the following events: - * - * - 'change': A change occurred, see below example - * - '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. - * - 'end': Emitted if the underlying stream is closed - * - 'close': Emitted if the underlying stream is closed - * - * ####Example: - * - * const User = conn.model('User', new Schema({ name: String })); - * - * const changeStream = conn.watch().on('change', data => console.log(data)); - * - * // Triggers a 'change' event on the change stream. - * await User.create({ name: 'test' }); - * - * @api public - * @param {Array} [pipeline] - * @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) - * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter - */ - -Connection.prototype.watch = function(pipeline, options) { - const disconnectedError = new MongooseError('Connection ' + this.id + - ' was disconnected when calling `watch()`'); - - const changeStreamThunk = cb => { - immediate(() => { - if (this.readyState === STATES.connecting) { - this.once('open', function() { - const driverChangeStream = this.db.watch(pipeline, options); - cb(null, driverChangeStream); - }); - } else if (this.readyState === STATES.disconnected && this.db == null) { - cb(disconnectedError); - } else { - const driverChangeStream = this.db.watch(pipeline, options); - cb(null, driverChangeStream); - } - }); - }; - - const changeStream = new ChangeStream(changeStreamThunk, pipeline, options); - return changeStream; -}; - -/** - * Returns an array of model names created on this connection. - * @api public - * @return {Array} - */ - -Connection.prototype.modelNames = function() { - return Object.keys(this.models); -}; - -/** - * @brief Returns if the connection requires authentication after it is opened. Generally if a - * username and password are both provided than authentication is needed, but in some cases a - * password is not required. - * @api private - * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false. - */ -Connection.prototype.shouldAuthenticate = function() { - return this.user != null && - (this.pass != null || this.authMechanismDoesNotRequirePassword()); -}; - -/** - * @brief Returns a boolean value that specifies if the current authentication mechanism needs a - * password to authenticate according to the auth objects passed into the openUri methods. - * @api private - * @return {Boolean} true if the authentication mechanism specified in the options object requires - * a password, otherwise false. - */ -Connection.prototype.authMechanismDoesNotRequirePassword = function() { - if (this.options && this.options.auth) { - return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0; - } - return true; -}; - -/** - * @brief Returns a boolean value that specifies if the provided objects object provides enough - * data to authenticate with. Generally this is true if the username and password are both specified - * but in some authentication methods, a password is not required for authentication so only a username - * is required. - * @param {Object} [options] the options object passed into the openUri methods. - * @api private - * @return {Boolean} true if the provided options object provides enough data to authenticate with, - * otherwise false. - */ -Connection.prototype.optionsProvideAuthenticationData = function(options) { - return (options) && - (options.user) && - ((options.pass) || this.authMechanismDoesNotRequirePassword()); -}; - -/** - * Returns the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance - * that this connection uses to talk to MongoDB. - * - * ####Example: - * const conn = await mongoose.createConnection('mongodb://localhost:27017/test'); - * - * conn.getClient(); // MongoClient { ... } - * - * @api public - * @return {MongoClient} - */ - -Connection.prototype.getClient = function getClient() { - return this.client; -}; - -/** - * Set the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance - * that this connection uses to talk to MongoDB. This is useful if you already have a MongoClient instance, and want to - * reuse it. - * - * ####Example: - * const client = await mongodb.MongoClient.connect('mongodb://localhost:27017/test'); - * - * const conn = mongoose.createConnection().setClient(client); - * - * conn.getClient(); // MongoClient { ... } - * conn.readyState; // 1, means 'CONNECTED' - * - * @api public - * @return {Connection} this - */ - -Connection.prototype.setClient = function setClient(client) { - if (!(client instanceof mongodb.MongoClient)) { - throw new MongooseError('Must call `setClient()` with an instance of MongoClient'); - } - if (this.client != null || this.readyState !== STATES.disconnected) { - throw new MongooseError('Cannot call `setClient()` on a connection that is already connected.'); - } - if (!client.isConnected()) { - throw new MongooseError('Cannot call `setClient()` with a MongoClient that is not connected.'); - } - - this._connectionString = client.s.url; - _setClient(this, client, { useUnifiedTopology: client.s.options.useUnifiedTopology }, client.s.options.dbName); - - return this; -}; - -/** - * Switches to a different database using the same connection pool. - * - * Returns a new connection object, with the new db. - * - * @method useDb - * @memberOf Connection - * @param {String} name The database name - * @param {Object} [options] - * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object. - * @return {Connection} New Connection Object - * @api public - */ - -/*! - * Module exports. - */ - -Connection.STATES = STATES; -module.exports = Connection; |