Cleanup
This commit is contained in:
521
node_modules/mongoose/lib/connection.js
generated
vendored
521
node_modules/mongoose/lib/connection.js
generated
vendored
@@ -22,7 +22,8 @@ const utils = require('./utils');
|
||||
|
||||
const parseConnectionString = require('mongodb/lib/core').parseConnectionString;
|
||||
|
||||
let id = 0;
|
||||
const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
|
||||
const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
|
||||
|
||||
/*!
|
||||
* A list of authentication mechanisms that don't require a password for authentication.
|
||||
@@ -57,7 +58,7 @@ function Connection(base) {
|
||||
this.base = base;
|
||||
this.collections = {};
|
||||
this.models = {};
|
||||
this.config = { autoIndex: true };
|
||||
this.config = {};
|
||||
this.replica = false;
|
||||
this.options = null;
|
||||
this.otherDbs = []; // FIXME: To be replaced with relatedDbs
|
||||
@@ -67,7 +68,12 @@ function Connection(base) {
|
||||
this._closeCalled = false;
|
||||
this._hasOpened = false;
|
||||
this.plugins = [];
|
||||
this.id = id++;
|
||||
if (typeof base === 'undefined' || !base.connections.length) {
|
||||
this.id = 0;
|
||||
} else {
|
||||
this.id = base.connections.length;
|
||||
}
|
||||
this._queue = [];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -113,11 +119,6 @@ Object.defineProperty(Connection.prototype, 'readyState', {
|
||||
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;
|
||||
}
|
||||
@@ -140,6 +141,10 @@ Object.defineProperty(Connection.prototype, 'readyState', {
|
||||
*/
|
||||
|
||||
Connection.prototype.get = function(key) {
|
||||
if (this.config.hasOwnProperty(key)) {
|
||||
return this.config[key];
|
||||
}
|
||||
|
||||
return get(this.options, key);
|
||||
};
|
||||
|
||||
@@ -164,6 +169,11 @@ Connection.prototype.get = function(key) {
|
||||
*/
|
||||
|
||||
Connection.prototype.set = function(key, val) {
|
||||
if (this.config.hasOwnProperty(key)) {
|
||||
this.config[key] = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
this.options = this.options || {};
|
||||
this.options[key] = val;
|
||||
return val;
|
||||
@@ -349,6 +359,18 @@ Object.defineProperty(Connection.prototype, 'pass', {
|
||||
|
||||
Connection.prototype.db;
|
||||
|
||||
/**
|
||||
* The MongoClient instance this connection uses to talk to MongoDB. Mongoose automatically sets this property
|
||||
* when the connection is opened.
|
||||
*
|
||||
* @property client
|
||||
* @memberOf Connection
|
||||
* @instance
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Connection.prototype.client;
|
||||
|
||||
/**
|
||||
* A hash of the global options that are associated with this connection
|
||||
*
|
||||
@@ -417,6 +439,77 @@ Connection.prototype.startSession = _wrapConnHelper(function startSession(option
|
||||
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!');
|
||||
* },{ readPreference: 'primary' }).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
|
||||
* @param {mongodb.TransactionOptions} [options] Optional settings for the transaction
|
||||
* @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.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Connection.prototype.transaction = function transaction(fn, options) {
|
||||
return this.startSession().then(session => {
|
||||
session[sessionNewDocuments] = new Map();
|
||||
return session.withTransaction(() => fn(session), options).
|
||||
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.
|
||||
@@ -476,20 +569,36 @@ function _wrapConnHelper(fn) {
|
||||
// as long as `mongoose.connect()` is called on the same tick.
|
||||
// Re: gh-8534
|
||||
immediate(() => {
|
||||
if (this.readyState === STATES.connecting) {
|
||||
this.once('open', function() {
|
||||
fn.apply(this, argsWithoutCb.concat([cb]));
|
||||
});
|
||||
if (this.readyState === STATES.connecting && this._shouldBufferCommands()) {
|
||||
this._queue.push({ fn: fn, ctx: this, args: argsWithoutCb.concat([cb]) });
|
||||
} else if (this.readyState === STATES.disconnected && this.db == null) {
|
||||
cb(disconnectedError);
|
||||
} else {
|
||||
fn.apply(this, argsWithoutCb.concat([cb]));
|
||||
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.get('bufferCommands') != null) {
|
||||
return this.base.get('bufferCommands');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* error
|
||||
*
|
||||
@@ -521,6 +630,11 @@ Connection.prototype.error = function(err, callback) {
|
||||
Connection.prototype.onOpen = function() {
|
||||
this.readyState = STATES.connected;
|
||||
|
||||
for (const d of this._queue) {
|
||||
d.fn.apply(d.ctx, d.args);
|
||||
}
|
||||
this._queue = [];
|
||||
|
||||
// avoid having the collection subscribe to our event emitter
|
||||
// to prevent 0.3 warning
|
||||
for (const i in this.collections) {
|
||||
@@ -538,6 +652,7 @@ Connection.prototype.onOpen = function() {
|
||||
* @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 {Number} [options.bufferTimeoutMS=10000] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered.
|
||||
* @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.
|
||||
@@ -547,25 +662,22 @@ Connection.prototype.onOpen = function() {
|
||||
* @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.useCreateIndex=false] 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.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 {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 {Boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection.
|
||||
* @param {Function} [callback]
|
||||
* @returns {Connection} this
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Connection.prototype.openUri = function(uri, options, callback) {
|
||||
this.readyState = STATES.connecting;
|
||||
this._closeCalled = false;
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = null;
|
||||
@@ -590,11 +702,32 @@ Connection.prototype.openUri = function(uri, options, callback) {
|
||||
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;
|
||||
@@ -631,7 +764,9 @@ Connection.prototype.openUri = function(uri, options, callback) {
|
||||
delete options.pass;
|
||||
|
||||
if (options.bufferCommands != null) {
|
||||
options.bufferMaxEntries = 0;
|
||||
if (options.bufferMaxEntries == null) {
|
||||
options.bufferMaxEntries = 0;
|
||||
}
|
||||
this.config.bufferCommands = options.bufferCommands;
|
||||
delete options.bufferCommands;
|
||||
}
|
||||
@@ -694,132 +829,18 @@ Connection.prototype.openUri = function(uri, options, callback) {
|
||||
});
|
||||
});
|
||||
|
||||
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 (_this.readyState !== STATES.connected) {
|
||||
_this.readyState = STATES.connected;
|
||||
_this.emit('reconnect');
|
||||
_this.emit('reconnected');
|
||||
}
|
||||
};
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const client = new mongodb.MongoClient(uri, options);
|
||||
_this.client = client;
|
||||
client.connect(function(error) {
|
||||
client.setMaxListeners(0);
|
||||
client.connect((error) => {
|
||||
if (error) {
|
||||
_this.readyState = STATES.disconnected;
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
const db = dbName != null ? client.db(dbName) : client.db();
|
||||
_this.db = db;
|
||||
|
||||
// `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 {
|
||||
_this.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 (_this.readyState === STATES.connected && allServersDisconnected) {
|
||||
// Implicitly emits 'disconnected'
|
||||
_this.readyState = STATES.disconnected;
|
||||
} else if (_this.readyState === STATES.disconnected && !allServersDisconnected) {
|
||||
_handleReconnect();
|
||||
}
|
||||
});
|
||||
|
||||
db.on('close', function() {
|
||||
const type = get(db, 's.topology.s.description.type', '');
|
||||
if (type !== 'ReplicaSetWithPrimary') {
|
||||
// Implicitly emits 'disconnected'
|
||||
_this.readyState = STATES.disconnected;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Backwards compat for mongoose 4.x
|
||||
db.on('reconnect', function() {
|
||||
_handleReconnect();
|
||||
});
|
||||
db.s.topology.on('reconnectFailed', function() {
|
||||
_this.emit('reconnectFailed');
|
||||
});
|
||||
|
||||
if (!options.useUnifiedTopology) {
|
||||
db.s.topology.on('left', function(data) {
|
||||
_this.emit('left', data);
|
||||
});
|
||||
}
|
||||
db.s.topology.on('joined', function(data) {
|
||||
_this.emit('joined', data);
|
||||
});
|
||||
db.s.topology.on('fullsetup', function(data) {
|
||||
_this.emit('fullsetup', data);
|
||||
});
|
||||
if (get(db, 's.topology.s.coreTopology.s.pool') != null) {
|
||||
db.s.topology.s.coreTopology.s.pool.on('attemptReconnect', function() {
|
||||
_this.emit('attemptReconnect');
|
||||
});
|
||||
}
|
||||
if (!options.useUnifiedTopology || !type.startsWith('ReplicaSet')) {
|
||||
db.on('close', function() {
|
||||
// Implicitly emits 'disconnected'
|
||||
_this.readyState = STATES.disconnected;
|
||||
});
|
||||
}
|
||||
|
||||
if (!options.useUnifiedTopology) {
|
||||
client.on('left', function() {
|
||||
if (_this.readyState === STATES.connected &&
|
||||
get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') {
|
||||
_this.readyState = STATES.disconnected;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
db.on('timeout', function() {
|
||||
_this.emit('timeout');
|
||||
});
|
||||
|
||||
delete _this.then;
|
||||
delete _this.catch;
|
||||
_this.readyState = STATES.connected;
|
||||
|
||||
for (const i in _this.collections) {
|
||||
if (utils.object.hasOwnProperty(_this.collections, i)) {
|
||||
_this.collections[i].onOpen();
|
||||
}
|
||||
}
|
||||
_setClient(_this, client, options, dbName);
|
||||
|
||||
resolve(_this);
|
||||
_this.emit('open');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -827,17 +848,22 @@ Connection.prototype.openUri = function(uri, options, callback) {
|
||||
this.$initialConnection = Promise.all([promise, parsePromise]).
|
||||
then(res => res[0]).
|
||||
catch(err => {
|
||||
this.readyState = STATES.disconnected;
|
||||
if (err != null && err.name === 'MongoServerSelectionError') {
|
||||
err = serverSelectionError.assimilateError(err);
|
||||
}
|
||||
|
||||
if (this.listeners('error').length > 0) {
|
||||
process.nextTick(() => this.emit('error', err));
|
||||
immediate(() => this.emit('error', err));
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
this.then = function(resolve, reject) {
|
||||
return this.$initialConnection.then(resolve, reject);
|
||||
return this.$initialConnection.then(() => {
|
||||
if (typeof resolve === 'function') {
|
||||
return resolve(_this);
|
||||
}
|
||||
}, reject);
|
||||
};
|
||||
this.catch = function(reject) {
|
||||
return this.$initialConnection.catch(reject);
|
||||
@@ -853,6 +879,126 @@ Connection.prototype.openUri = function(uri, options, callback) {
|
||||
return this;
|
||||
};
|
||||
|
||||
function _setClient(conn, client, options, dbName) {
|
||||
const db = dbName != null ? client.db(dbName) : client.db();
|
||||
conn.db = db;
|
||||
conn.client = client;
|
||||
conn._closeCalled = client._closeCalled;
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
client.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.s.topology.on('reconnectFailed', function() {
|
||||
conn.emit('reconnectFailed');
|
||||
});
|
||||
|
||||
if (!options.useUnifiedTopology) {
|
||||
client.on('reconnect', function() {
|
||||
_handleReconnect();
|
||||
});
|
||||
|
||||
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) {
|
||||
client.on('close', function() {
|
||||
// Implicitly emits 'disconnected'
|
||||
conn.readyState = STATES.disconnected;
|
||||
});
|
||||
} else if (!type.startsWith('ReplicaSet')) {
|
||||
client.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;
|
||||
}
|
||||
});
|
||||
|
||||
client.on('timeout', function() {
|
||||
conn.emit('timeout');
|
||||
});
|
||||
}
|
||||
|
||||
delete conn.then;
|
||||
delete conn.catch;
|
||||
|
||||
conn.onOpen();
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
@@ -896,11 +1042,25 @@ Connection.prototype.close = function(force, callback) {
|
||||
*/
|
||||
Connection.prototype._close = function(force, callback) {
|
||||
const _this = this;
|
||||
const closeCalled = this._closeCalled;
|
||||
this._closeCalled = true;
|
||||
if (this.client != null) {
|
||||
this.client._closeCalled = true;
|
||||
}
|
||||
|
||||
switch (this.readyState) {
|
||||
case STATES.disconnected:
|
||||
callback();
|
||||
if (closeCalled) {
|
||||
callback();
|
||||
} else {
|
||||
this.doClose(force, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
_this.onClose(force);
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case STATES.connected:
|
||||
@@ -962,7 +1122,11 @@ Connection.prototype.onClose = function(force) {
|
||||
*/
|
||||
|
||||
Connection.prototype.collection = function(name, options) {
|
||||
options = options ? utils.clone(options) : {};
|
||||
const defaultOptions = {
|
||||
autoIndex: this.config.autoIndex != null ? this.config.autoIndex : this.base.options.autoIndex,
|
||||
autoCreate: this.config.autoCreate != null ? this.config.autoCreate : this.base.options.autoCreate
|
||||
};
|
||||
options = Object.assign({}, defaultOptions, options ? utils.clone(options) : {});
|
||||
options.$wasForceClosed = this.$wasForceClosed;
|
||||
if (!(name in this.collections)) {
|
||||
this.collections[name] = new Collection(name, this, options);
|
||||
@@ -997,17 +1161,17 @@ Connection.prototype.plugin = function(fn, opts) {
|
||||
/**
|
||||
* Defines or retrieves a model.
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var db = mongoose.createConnection(..);
|
||||
* const mongoose = require('mongoose');
|
||||
* const db = mongoose.createConnection(..);
|
||||
* db.model('Venue', new Schema(..));
|
||||
* var Ticket = db.model('Ticket', new Schema(..));
|
||||
* var Venue = db.model('Venue');
|
||||
* 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:
|
||||
*
|
||||
* var schema = new Schema({ name: String }, { collection: 'actor' });
|
||||
* const schema = new Schema({ name: String }, { collection: 'actor' });
|
||||
*
|
||||
* // or
|
||||
*
|
||||
@@ -1015,18 +1179,20 @@ Connection.prototype.plugin = function(fn, opts) {
|
||||
*
|
||||
* // or
|
||||
*
|
||||
* var collectionName = 'actor'
|
||||
* var M = conn.model('Actor', schema, collectionName)
|
||||
* 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
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.overwriteModels=false] If true, overwrite existing models with the same name to avoid `OverwriteModelError`
|
||||
* @see Mongoose#model #index_Mongoose-model
|
||||
* @return {Model} The compiled model
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Connection.prototype.model = function(name, schema, collection) {
|
||||
Connection.prototype.model = function(name, schema, collection, options) {
|
||||
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 ' +
|
||||
@@ -1053,7 +1219,9 @@ Connection.prototype.model = function(name, schema, collection) {
|
||||
'schema or a POJO');
|
||||
}
|
||||
|
||||
if (this.models[name] && !collection) {
|
||||
const defaultOptions = { cache: false, overwriteModels: this.base.options.overwriteModels };
|
||||
const opts = Object.assign(defaultOptions, options, { connection: this });
|
||||
if (this.models[name] && !collection && opts.overwriteModels !== true) {
|
||||
// model exists but we are not subclassing with custom collection
|
||||
if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) {
|
||||
throw new MongooseError.OverwriteModelError(name);
|
||||
@@ -1061,7 +1229,6 @@ Connection.prototype.model = function(name, schema, collection) {
|
||||
return this.models[name];
|
||||
}
|
||||
|
||||
const opts = { cache: false, connection: this };
|
||||
let model;
|
||||
|
||||
if (schema && schema.instanceOfSchema) {
|
||||
@@ -1146,6 +1313,8 @@ Connection.prototype.deleteModel = function(name) {
|
||||
delete this.models[name];
|
||||
delete this.collections[collectionName];
|
||||
delete this.base.modelSchemas[name];
|
||||
|
||||
this.emit('deleteModel', model);
|
||||
} else if (name instanceof RegExp) {
|
||||
const pattern = name;
|
||||
const names = this.modelNames();
|
||||
@@ -1267,6 +1436,57 @@ Connection.prototype.optionsProvideAuthenticationData = function(options) {
|
||||
((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.
|
||||
*
|
||||
@@ -1277,6 +1497,7 @@ Connection.prototype.optionsProvideAuthenticationData = function(options) {
|
||||
* @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.
|
||||
* @param {Boolean} [options.noListener=false] If true, the connection object will not make the db listen to events on the original connection. See [issue #9961](https://github.com/Automattic/mongoose/issues/9961).
|
||||
* @return {Connection} New Connection Object
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user