You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

802 lines
19 KiB

  1. 'use strict';
  2. /*!
  3. * Module dependencies.
  4. */
  5. var Schema = require('./schema'),
  6. SchemaType = require('./schematype'),
  7. VirtualType = require('./virtualtype'),
  8. STATES = require('./connectionstate'),
  9. Types = require('./types'),
  10. Query = require('./query'),
  11. Model = require('./model'),
  12. Document = require('./document'),
  13. utils = require('./utils'),
  14. format = utils.toCollectionName,
  15. pkg = require('../package.json');
  16. var querystring = require('querystring');
  17. var Aggregate = require('./aggregate');
  18. var PromiseProvider = require('./promise_provider');
  19. /**
  20. * Mongoose constructor.
  21. *
  22. * The exports object of the `mongoose` module is an instance of this class.
  23. * Most apps will only use this one instance.
  24. *
  25. * @api public
  26. */
  27. function Mongoose() {
  28. this.connections = [];
  29. this.plugins = [];
  30. this.models = {};
  31. this.modelSchemas = {};
  32. // default global options
  33. this.options = {
  34. pluralization: true
  35. };
  36. var conn = this.createConnection(); // default connection
  37. conn.models = this.models;
  38. }
  39. /**
  40. * Expose connection states for user-land
  41. *
  42. */
  43. Mongoose.prototype.STATES = STATES;
  44. /**
  45. * Sets mongoose options
  46. *
  47. * ####Example:
  48. *
  49. * mongoose.set('test', value) // sets the 'test' option to `value`
  50. *
  51. * mongoose.set('debug', true) // enable logging collection methods + arguments to the console
  52. *
  53. * mongoose.set('debug', function(collectionName, methodName, arg1, arg2...) {}); // use custom function to log collection methods + arguments
  54. *
  55. * @param {String} key
  56. * @param {String|Function} value
  57. * @api public
  58. */
  59. Mongoose.prototype.set = function(key, value) {
  60. if (arguments.length === 1) {
  61. return this.options[key];
  62. }
  63. this.options[key] = value;
  64. return this;
  65. };
  66. Mongoose.prototype.set.$hasSideEffects = true;
  67. /**
  68. * Gets mongoose options
  69. *
  70. * ####Example:
  71. *
  72. * mongoose.get('test') // returns the 'test' value
  73. *
  74. * @param {String} key
  75. * @method get
  76. * @api public
  77. */
  78. Mongoose.prototype.get = Mongoose.prototype.set;
  79. /*!
  80. * ReplSet connection string check.
  81. */
  82. var rgxReplSet = /^.+,.+$/;
  83. /**
  84. * Checks if ?replicaSet query parameter is specified in URI
  85. *
  86. * ####Example:
  87. *
  88. * checkReplicaSetInUri('localhost:27000?replicaSet=rs0'); // true
  89. *
  90. * @param {String} uri
  91. * @return {boolean}
  92. * @api private
  93. */
  94. var checkReplicaSetInUri = function(uri) {
  95. if (!uri) {
  96. return false;
  97. }
  98. var queryStringStart = uri.indexOf('?');
  99. var isReplicaSet = false;
  100. if (queryStringStart !== -1) {
  101. try {
  102. var obj = querystring.parse(uri.substr(queryStringStart + 1));
  103. if (obj && obj.replicaSet) {
  104. isReplicaSet = true;
  105. }
  106. } catch (e) {
  107. return false;
  108. }
  109. }
  110. return isReplicaSet;
  111. };
  112. /**
  113. * Creates a Connection instance.
  114. *
  115. * Each `connection` instance maps to a single database. This method is helpful when mangaging multiple db connections.
  116. *
  117. * If arguments are passed, they are proxied to either [Connection#open](#connection_Connection-open) or [Connection#openSet](#connection_Connection-openSet) appropriately. This means we can pass `db`, `server`, and `replset` options to the driver. _Note that the `safe` option specified in your schema will overwrite the `safe` db option specified here unless you set your schemas `safe` option to `undefined`. See [this](/docs/guide.html#safe) for more information._
  118. *
  119. * _Options passed take precedence over options included in connection strings._
  120. *
  121. * ####Example:
  122. *
  123. * // with mongodb:// URI
  124. * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database');
  125. *
  126. * // and options
  127. * var opts = { db: { native_parser: true }}
  128. * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database', opts);
  129. *
  130. * // replica sets
  131. * db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database');
  132. *
  133. * // and options
  134. * var opts = { replset: { strategy: 'ping', rs_name: 'testSet' }}
  135. * db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts);
  136. *
  137. * // with [host, database_name[, port] signature
  138. * db = mongoose.createConnection('localhost', 'database', port)
  139. *
  140. * // and options
  141. * var opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' }
  142. * db = mongoose.createConnection('localhost', 'database', port, opts)
  143. *
  144. * // initialize now, connect later
  145. * db = mongoose.createConnection();
  146. * db.open('localhost', 'database', port, [opts]);
  147. *
  148. * @param {String} [uri] a mongodb:// URI
  149. * @param {Object} [options] options to pass to the driver
  150. * @param {Object} [options.config] mongoose-specific options
  151. * @param {Boolean} [options.config.autoIndex] set to false to disable automatic index creation for all models associated with this connection.
  152. * @see Connection#open #connection_Connection-open
  153. * @see Connection#openSet #connection_Connection-openSet
  154. * @return {Connection} the created Connection object
  155. * @api public
  156. */
  157. Mongoose.prototype.createConnection = function(uri, options) {
  158. var conn = new Connection(this);
  159. this.connections.push(conn);
  160. if (arguments.length) {
  161. if (rgxReplSet.test(arguments[0]) || checkReplicaSetInUri(arguments[0])) {
  162. conn.openSet.apply(conn, arguments);
  163. } else if (options && options.replset &&
  164. (options.replset.replicaSet || options.replset.rs_name)) {
  165. conn.openSet.apply(conn, arguments);
  166. } else {
  167. conn.open.apply(conn, arguments);
  168. }
  169. }
  170. return conn;
  171. };
  172. Mongoose.prototype.createConnection.$hasSideEffects = true;
  173. /**
  174. * Opens the default mongoose connection.
  175. *
  176. * If arguments are passed, they are proxied to either
  177. * [Connection#open](#connection_Connection-open) or
  178. * [Connection#openSet](#connection_Connection-openSet) appropriately.
  179. *
  180. * _Options passed take precedence over options included in connection strings._
  181. *
  182. * ####Example:
  183. *
  184. * mongoose.connect('mongodb://user:pass@localhost:port/database');
  185. *
  186. * // replica sets
  187. * var uri = 'mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/mydatabase';
  188. * mongoose.connect(uri);
  189. *
  190. * // with options
  191. * mongoose.connect(uri, options);
  192. *
  193. * // connecting to multiple mongos
  194. * var uri = 'mongodb://hostA:27501,hostB:27501';
  195. * var opts = { mongos: true };
  196. * mongoose.connect(uri, opts);
  197. *
  198. * // optional callback that gets fired when initial connection completed
  199. * var uri = 'mongodb://nonexistent.domain:27000';
  200. * mongoose.connect(uri, function(error) {
  201. * // if error is truthy, the initial connection failed.
  202. * })
  203. *
  204. * @param {String} uri(s)
  205. * @param {Object} [options]
  206. * @param {Function} [callback]
  207. * @see Mongoose#createConnection #index_Mongoose-createConnection
  208. * @api public
  209. * @return {MongooseThenable} pseudo-promise wrapper around this
  210. */
  211. Mongoose.prototype.connect = function() {
  212. var conn = this.connection;
  213. if (rgxReplSet.test(arguments[0]) || checkReplicaSetInUri(arguments[0])) {
  214. return new MongooseThenable(this, conn.openSet.apply(conn, arguments));
  215. }
  216. return new MongooseThenable(this, conn.open.apply(conn, arguments));
  217. };
  218. Mongoose.prototype.connect.$hasSideEffects = true;
  219. /**
  220. * Disconnects all connections.
  221. *
  222. * @param {Function} [fn] called after all connection close.
  223. * @return {MongooseThenable} pseudo-promise wrapper around this
  224. * @api public
  225. */
  226. Mongoose.prototype.disconnect = function(fn) {
  227. var error;
  228. this.connections.forEach(function(conn) {
  229. conn.close(function(err) {
  230. if (error) {
  231. return;
  232. }
  233. if (err) {
  234. error = err;
  235. }
  236. });
  237. });
  238. var Promise = PromiseProvider.get();
  239. return new MongooseThenable(this, new Promise.ES6(function(resolve, reject) {
  240. fn && fn(error);
  241. if (error) {
  242. reject(error);
  243. return;
  244. }
  245. resolve();
  246. }));
  247. };
  248. Mongoose.prototype.disconnect.$hasSideEffects = true;
  249. /**
  250. * Defines a model or retrieves it.
  251. *
  252. * Models defined on the `mongoose` instance are available to all connection created by the same `mongoose` instance.
  253. *
  254. * ####Example:
  255. *
  256. * var mongoose = require('mongoose');
  257. *
  258. * // define an Actor model with this mongoose instance
  259. * mongoose.model('Actor', new Schema({ name: String }));
  260. *
  261. * // create a new connection
  262. * var conn = mongoose.createConnection(..);
  263. *
  264. * // retrieve the Actor model
  265. * var Actor = conn.model('Actor');
  266. *
  267. * _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._
  268. *
  269. * ####Example:
  270. *
  271. * var schema = new Schema({ name: String }, { collection: 'actor' });
  272. *
  273. * // or
  274. *
  275. * schema.set('collection', 'actor');
  276. *
  277. * // or
  278. *
  279. * var collectionName = 'actor'
  280. * var M = mongoose.model('Actor', schema, collectionName)
  281. *
  282. * @param {String} name model name
  283. * @param {Schema} [schema]
  284. * @param {String} [collection] name (optional, induced from model name)
  285. * @param {Boolean} [skipInit] whether to skip initialization (defaults to false)
  286. * @api public
  287. */
  288. Mongoose.prototype.model = function(name, schema, collection, skipInit) {
  289. if (typeof schema === 'string') {
  290. collection = schema;
  291. schema = false;
  292. }
  293. if (utils.isObject(schema) && !(schema.instanceOfSchema)) {
  294. schema = new Schema(schema);
  295. }
  296. if (typeof collection === 'boolean') {
  297. skipInit = collection;
  298. collection = null;
  299. }
  300. // handle internal options from connection.model()
  301. var options;
  302. if (skipInit && utils.isObject(skipInit)) {
  303. options = skipInit;
  304. skipInit = true;
  305. } else {
  306. options = {};
  307. }
  308. // look up schema for the collection.
  309. if (!this.modelSchemas[name]) {
  310. if (schema) {
  311. // cache it so we only apply plugins once
  312. this.modelSchemas[name] = schema;
  313. this._applyPlugins(schema);
  314. } else {
  315. throw new mongoose.Error.MissingSchemaError(name);
  316. }
  317. }
  318. var model;
  319. var sub;
  320. // connection.model() may be passing a different schema for
  321. // an existing model name. in this case don't read from cache.
  322. if (this.models[name] && options.cache !== false) {
  323. if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) {
  324. throw new mongoose.Error.OverwriteModelError(name);
  325. }
  326. if (collection) {
  327. // subclass current model with alternate collection
  328. model = this.models[name];
  329. schema = model.prototype.schema;
  330. sub = model.__subclass(this.connection, schema, collection);
  331. // do not cache the sub model
  332. return sub;
  333. }
  334. return this.models[name];
  335. }
  336. // ensure a schema exists
  337. if (!schema) {
  338. schema = this.modelSchemas[name];
  339. if (!schema) {
  340. throw new mongoose.Error.MissingSchemaError(name);
  341. }
  342. }
  343. // Apply relevant "global" options to the schema
  344. if (!('pluralization' in schema.options)) schema.options.pluralization = this.options.pluralization;
  345. if (!collection) {
  346. collection = schema.get('collection') || format(name, schema.options);
  347. }
  348. var connection = options.connection || this.connection;
  349. model = Model.compile(name, schema, collection, connection, this);
  350. if (!skipInit) {
  351. model.init();
  352. }
  353. if (options.cache === false) {
  354. return model;
  355. }
  356. this.models[name] = model;
  357. return this.models[name];
  358. };
  359. Mongoose.prototype.model.$hasSideEffects = true;
  360. /**
  361. * Returns an array of model names created on this instance of Mongoose.
  362. *
  363. * ####Note:
  364. *
  365. * _Does not include names of models created using `connection.model()`._
  366. *
  367. * @api public
  368. * @return {Array}
  369. */
  370. Mongoose.prototype.modelNames = function() {
  371. var names = Object.keys(this.models);
  372. return names;
  373. };
  374. Mongoose.prototype.modelNames.$hasSideEffects = true;
  375. /**
  376. * Applies global plugins to `schema`.
  377. *
  378. * @param {Schema} schema
  379. * @api private
  380. */
  381. Mongoose.prototype._applyPlugins = function(schema) {
  382. if (schema.$globalPluginsApplied) {
  383. return;
  384. }
  385. var i;
  386. var len;
  387. for (i = 0, len = this.plugins.length; i < len; ++i) {
  388. schema.plugin(this.plugins[i][0], this.plugins[i][1]);
  389. }
  390. schema.$globalPluginsApplied = true;
  391. for (i = 0, len = schema.childSchemas.length; i < len; ++i) {
  392. this._applyPlugins(schema.childSchemas[i]);
  393. }
  394. };
  395. Mongoose.prototype._applyPlugins.$hasSideEffects = true;
  396. /**
  397. * Declares a global plugin executed on all Schemas.
  398. *
  399. * Equivalent to calling `.plugin(fn)` on each Schema you create.
  400. *
  401. * @param {Function} fn plugin callback
  402. * @param {Object} [opts] optional options
  403. * @return {Mongoose} this
  404. * @see plugins ./plugins.html
  405. * @api public
  406. */
  407. Mongoose.prototype.plugin = function(fn, opts) {
  408. this.plugins.push([fn, opts]);
  409. return this;
  410. };
  411. Mongoose.prototype.plugin.$hasSideEffects = true;
  412. /**
  413. * The default connection of the mongoose module.
  414. *
  415. * ####Example:
  416. *
  417. * var mongoose = require('mongoose');
  418. * mongoose.connect(...);
  419. * mongoose.connection.on('error', cb);
  420. *
  421. * This is the connection used by default for every model created using [mongoose.model](#index_Mongoose-model).
  422. *
  423. * @property connection
  424. * @return {Connection}
  425. * @api public
  426. */
  427. Mongoose.prototype.__defineGetter__('connection', function() {
  428. return this.connections[0];
  429. });
  430. Mongoose.prototype.__defineSetter__('connection', function(v) {
  431. this.connections[0] = v;
  432. });
  433. /*!
  434. * Driver depentend APIs
  435. */
  436. var driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native';
  437. /*!
  438. * Connection
  439. */
  440. var Connection = require(driver + '/connection');
  441. /*!
  442. * Collection
  443. */
  444. var Collection = require(driver + '/collection');
  445. /**
  446. * The Mongoose Aggregate constructor
  447. *
  448. * @method Aggregate
  449. * @api public
  450. */
  451. Mongoose.prototype.Aggregate = Aggregate;
  452. /**
  453. * The Mongoose Collection constructor
  454. *
  455. * @method Collection
  456. * @api public
  457. */
  458. Mongoose.prototype.Collection = Collection;
  459. /**
  460. * The Mongoose [Connection](#connection_Connection) constructor
  461. *
  462. * @method Connection
  463. * @api public
  464. */
  465. Mongoose.prototype.Connection = Connection;
  466. /**
  467. * The Mongoose version
  468. *
  469. * @property version
  470. * @api public
  471. */
  472. Mongoose.prototype.version = pkg.version;
  473. /**
  474. * The Mongoose constructor
  475. *
  476. * The exports of the mongoose module is an instance of this class.
  477. *
  478. * ####Example:
  479. *
  480. * var mongoose = require('mongoose');
  481. * var mongoose2 = new mongoose.Mongoose();
  482. *
  483. * @method Mongoose
  484. * @api public
  485. */
  486. Mongoose.prototype.Mongoose = Mongoose;
  487. /**
  488. * The Mongoose [Schema](#schema_Schema) constructor
  489. *
  490. * ####Example:
  491. *
  492. * var mongoose = require('mongoose');
  493. * var Schema = mongoose.Schema;
  494. * var CatSchema = new Schema(..);
  495. *
  496. * @method Schema
  497. * @api public
  498. */
  499. Mongoose.prototype.Schema = Schema;
  500. /**
  501. * The Mongoose [SchemaType](#schematype_SchemaType) constructor
  502. *
  503. * @method SchemaType
  504. * @api public
  505. */
  506. Mongoose.prototype.SchemaType = SchemaType;
  507. /**
  508. * The various Mongoose SchemaTypes.
  509. *
  510. * ####Note:
  511. *
  512. * _Alias of mongoose.Schema.Types for backwards compatibility._
  513. *
  514. * @property SchemaTypes
  515. * @see Schema.SchemaTypes #schema_Schema.Types
  516. * @api public
  517. */
  518. Mongoose.prototype.SchemaTypes = Schema.Types;
  519. /**
  520. * The Mongoose [VirtualType](#virtualtype_VirtualType) constructor
  521. *
  522. * @method VirtualType
  523. * @api public
  524. */
  525. Mongoose.prototype.VirtualType = VirtualType;
  526. /**
  527. * The various Mongoose Types.
  528. *
  529. * ####Example:
  530. *
  531. * var mongoose = require('mongoose');
  532. * var array = mongoose.Types.Array;
  533. *
  534. * ####Types:
  535. *
  536. * - [ObjectId](#types-objectid-js)
  537. * - [Buffer](#types-buffer-js)
  538. * - [SubDocument](#types-embedded-js)
  539. * - [Array](#types-array-js)
  540. * - [DocumentArray](#types-documentarray-js)
  541. *
  542. * Using this exposed access to the `ObjectId` type, we can construct ids on demand.
  543. *
  544. * var ObjectId = mongoose.Types.ObjectId;
  545. * var id1 = new ObjectId;
  546. *
  547. * @property Types
  548. * @api public
  549. */
  550. Mongoose.prototype.Types = Types;
  551. /**
  552. * The Mongoose [Query](#query_Query) constructor.
  553. *
  554. * @method Query
  555. * @api public
  556. */
  557. Mongoose.prototype.Query = Query;
  558. /**
  559. * The Mongoose [Promise](#promise_Promise) constructor.
  560. *
  561. * @method Promise
  562. * @api public
  563. */
  564. Object.defineProperty(Mongoose.prototype, 'Promise', {
  565. get: function() {
  566. return PromiseProvider.get();
  567. },
  568. set: function(lib) {
  569. PromiseProvider.set(lib);
  570. }
  571. });
  572. /**
  573. * Storage layer for mongoose promises
  574. *
  575. * @method PromiseProvider
  576. * @api public
  577. */
  578. Mongoose.prototype.PromiseProvider = PromiseProvider;
  579. /**
  580. * The Mongoose [Model](#model_Model) constructor.
  581. *
  582. * @method Model
  583. * @api public
  584. */
  585. Mongoose.prototype.Model = Model;
  586. /**
  587. * The Mongoose [Document](#document-js) constructor.
  588. *
  589. * @method Document
  590. * @api public
  591. */
  592. Mongoose.prototype.Document = Document;
  593. /**
  594. * The Mongoose DocumentProvider constructor.
  595. *
  596. * @method DocumentProvider
  597. * @api public
  598. */
  599. Mongoose.prototype.DocumentProvider = require('./document_provider');
  600. /**
  601. * The [MongooseError](#error_MongooseError) constructor.
  602. *
  603. * @method Error
  604. * @api public
  605. */
  606. Mongoose.prototype.Error = require('./error');
  607. /**
  608. * The Mongoose CastError constructor
  609. *
  610. * @method CastError
  611. * @param {String} type The name of the type
  612. * @param {Any} value The value that failed to cast
  613. * @param {String} path The path `a.b.c` in the doc where this cast error occurred
  614. * @param {Error} [reason] The original error that was thrown
  615. * @api public
  616. */
  617. Mongoose.prototype.CastError = require('./error/cast');
  618. /**
  619. * The [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver Mongoose uses.
  620. *
  621. * @property mongo
  622. * @api public
  623. */
  624. Mongoose.prototype.mongo = require('mongodb');
  625. /**
  626. * The [mquery](https://github.com/aheckmann/mquery) query builder Mongoose uses.
  627. *
  628. * @property mquery
  629. * @api public
  630. */
  631. Mongoose.prototype.mquery = require('mquery');
  632. /**
  633. * Wraps the given Mongoose instance into a thenable (pseudo-promise). This
  634. * is so `connect()` and `disconnect()` can return a thenable while maintaining
  635. * backwards compatibility.
  636. *
  637. * @api private
  638. */
  639. function MongooseThenable(mongoose, promise) {
  640. var _this = this;
  641. for (var key in mongoose) {
  642. if (typeof mongoose[key] === 'function' && mongoose[key].$hasSideEffects) {
  643. (function(key) {
  644. _this[key] = function() {
  645. return mongoose[key].apply(mongoose, arguments);
  646. };
  647. })(key);
  648. } else if (['connection', 'connections'].indexOf(key) !== -1) {
  649. _this[key] = mongoose[key];
  650. }
  651. }
  652. this.$opPromise = promise;
  653. }
  654. MongooseThenable.prototype = new Mongoose;
  655. /**
  656. * Ability to use mongoose object as a pseudo-promise so `.connect().then()`
  657. * and `.disconnect().then()` are viable.
  658. *
  659. * @param {Function} onFulfilled
  660. * @param {Function} onRejected
  661. * @return {Promise}
  662. * @api private
  663. */
  664. MongooseThenable.prototype.then = function(onFulfilled, onRejected) {
  665. var Promise = PromiseProvider.get();
  666. if (!this.$opPromise) {
  667. return new Promise.ES6(function(resolve, reject) {
  668. reject(new Error('Can only call `.then()` if connect() or disconnect() ' +
  669. 'has been called'));
  670. }).then(onFulfilled, onRejected);
  671. }
  672. this.$opPromise.$hasHandler = true;
  673. return this.$opPromise.then(onFulfilled, onRejected);
  674. };
  675. /**
  676. * Ability to use mongoose object as a pseudo-promise so `.connect().then()`
  677. * and `.disconnect().then()` are viable.
  678. *
  679. * @param {Function} onFulfilled
  680. * @param {Function} onRejected
  681. * @return {Promise}
  682. * @api private
  683. */
  684. MongooseThenable.prototype.catch = function(onRejected) {
  685. return this.then(null, onRejected);
  686. };
  687. /*!
  688. * The exports object is an instance of Mongoose.
  689. *
  690. * @api public
  691. */
  692. var mongoose = module.exports = exports = new Mongoose;