Files

Return to Package Diff Home.
Brought to you by Intrinsic.

Package Diff: mongoose @ 5.4.19 .. 5.5.0

History.md

@@ -1,3 +1,63 @@
+5.5.0 / 2019-04-08
+==================
+ * feat(model): support applying hooks to custom static functions #5982
+ * feat(populate): support specifying a function as `match` #7397
+ * perf(buffer): avoid calling `defineProperties()` in Buffer constructor #7331
+ * feat(connection): add `plugin()` for connection-scoped plugins #7378
+ * feat(model): add Model#deleteOne() and corresponding hooks #7538
+ * feat(query): support hooks for `Query#distinct()` #5938
+ * feat(model): print warning when calling create() incorrectly with a session #7535
+ * feat(document): add Document#isEmpty() and corresponding helpers for nested paths #5369
+ * feat(document): add `getters` option to Document#get() #7233
+ feat(query): add Query#projection() to get or overwrite the current projection #7384
+ * fix(document): set full validator path on validatorProperties if `propsParameter` set on validator #7447
+ * feat(document): add Document#directModifiedPaths() #7373
+ * feat(document): add $locals property #7691
+ * feat(document): add validateUpdatedOnly option that only validates modified paths in `save()` #7492 [captaincaius](https://github.com/captaincaius)
+ * chore: upgrade MongoDB driver to v3.2.0 #7641
+ * fix(schematype): deprecate `isAsync` option for custom validators #6700
+ * chore(mongoose): deprecate global.MONGOOSE_DRIVER_PATH so we can be webpack-warning-free in 6.0 #7501
+
+5.4.23 / 2019-04-08
+===================
+ * fix(document): report cast error when string path in schema is an array in MongoDB #7619
+ * fix(query): set deletedCount on result of remove() #7629
+ * docs(subdocs): add note about parent() and ownerDocument() to subdocument docs #7576
+
+5.4.22 / 2019-04-04
+===================
+ * fix(aggregate): allow modifying options in pre('aggregate') hook #7606
+ * fix(map): correctly init maps of maps when loading from MongoDB #7630
+ * docs(model+query): add `omitUndefined` option to docs for updateX() and findOneAndX() #3486
+ * docs: removed duplicate Query.prototype.merge() reference from doc #7684 [shihabmridha](https://github.com/shihabmridha)
+ * docs(schema): fix shardKey type to object instead of bool #7668 [kyletsang](https://github.com/kyletsang)
+ * docs(api): fix `Model.prototypedelete` link #7665 [pixcai](https://github.com/pixcai)
+
+5.4.21 / 2019-04-02
+===================
+ * fix(updateValidators): run update validators correctly on Decimal128 paths #7561
+ * fix(update): cast array filters in nested doc arrays correctly #7603
+ * fix(document): allow .get() + .set() with aliased paths #7592
+ * fix(document): ensure custom getters on single nested subdocs don't get persisted if toObject.getters = true #7601
+ * fix(document): support setting subdoc path to subdoc copied using object rest `{...doc}` #7645
+ * docs(schema): correct out-of-date list of reserved words #7593
+ * docs(model+query): add link to update results docs and examples of using results of updateOne(), etc. #7582
+ * docs: use atomic as opposed to $atomic consistently #7649 [720degreeLotus](https://github.com/720degreeLotus)
+
+5.4.20 / 2019-03-25
+===================
+ * docs(tutorials): add tutorial about `lean()` #7640
+ * fix(discriminator): fix wrong modelName being used as value to partialFilterExpression index #7635 #7634 [egorovli](https://github.com/egorovli)
+ * fix(document): allow setters to modify `this` when overwriting single nested subdoc #7585
+ * fix(populate): handle count option correctly with multiple docs #7573
+ * fix(date): support declaring min/max validators as functions #7600 [ChienDevIT](https://github.com/ChienDevIT)
+ * fix(discriminator): avoid projecting in embedded discriminator if only auto-selected path is discriminator key #7574
+ * fix(discriminator): use discriminator model when using `new BaseModel()` with discriminator key #7586
+ * fix(timestamps): avoid throwing if doc array has timestamps and array is undefined #7625 [serg33v](https://github.com/serg33v)
+ * docs(document): explain DocumentNotFoundError in save() docs #7580
+ * docs(query): fix .all() param type and add example #7612 [720degreeLotus](https://github.com/720degreeLotus)
+ * docs: add useNewUrlParser to mongoose.connect for some pages #7615 [YC](https://github.com/YC)
+
5.4.19 / 2019-03-11
===================
* fix(mongoose): ensure virtuals set on subdocs in global plugins get applied #7592

lib/aggregate.js

@@ -917,11 +917,10 @@
throw new Error('Aggregate not bound to any Model');
}
const model = this._model;
- const options = utils.clone(this.options || {});
const pipeline = this._pipeline;
const collection = this._model.collection;
- if (options && options.cursor) {
+ if (this.options && this.options.cursor) {
return new AggregationCursor(this);
}
@@ -941,6 +940,7 @@
});
}
+ const options = utils.clone(this.options || {});
collection.aggregate(pipeline, options, (error, cursor) => {
if (error) {
const _opts = { error: error };

lib/cast/string.js

@@ -27,7 +27,9 @@
// Re: gh-647 and gh-3030, we're ok with casting using `toString()`
// **unless** its the default Object.toString, because "[object Object]"
// doesn't really qualify as useful data
- if (value.toString && value.toString !== Object.prototype.toString) {
+ if (value.toString &&
+ value.toString !== Object.prototype.toString &&
+ !Array.isArray(value)) {
return value.toString();
}

lib/connection.js

@@ -10,6 +10,7 @@
const STATES = require('./connectionstate');
const MongooseError = require('./error');
const PromiseProvider = require('./promise_provider');
+const applyPlugins = require('./helpers/schema/applyPlugins');
const get = require('./helpers/get');
const mongodb = require('mongodb');
const utils = require('./utils');
@@ -59,6 +60,7 @@
this._readyState = STATES.disconnected;
this._closeCalled = false;
this._hasOpened = false;
+ this.plugins = [];
this.$internalEmitter = new EventEmitter();
this.$internalEmitter.setMaxListeners(0);
@@ -148,6 +150,29 @@
Connection.prototype.name;
/**
+ * 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
*
@@ -323,6 +348,12 @@
* 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}
@@ -724,6 +755,30 @@
};
/**
+ * 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.
*
* var mongoose = require('mongoose');
@@ -794,6 +849,8 @@
let model;
if (schema && schema.instanceOfSchema) {
+ applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied');
+
// compile a model
model = this.base.model(fn || name, schema, collection, opts);

lib/document.js

@@ -136,6 +136,7 @@
}
this.$__._id = this._id;
+ this.$locals = {};
if (!schema.options.strict && obj) {
const _this = this;
@@ -178,6 +179,35 @@
Document.prototype.schema;
/**
+ * Empty object that you can use for storing properties on the document. This
+ * is handy for passing data to middleware without conflicting with Mongoose
+ * internals.
+ *
+ * ####Example:
+ *
+ * schema.pre('save', function() {
+ * // Mongoose will set `isNew` to `false` if `save()` succeeds
+ * this.$locals.wasNew = this.isNew;
+ * });
+ *
+ * schema.post('save', function() {
+ * // Prints true if `isNew` was set before `save()`
+ * console.log(this.$locals.wasNew);
+ * });
+ *
+ * @api public
+ * @property $locals
+ * @memberOf Document
+ * @instance
+ */
+
+Object.defineProperty(Document.prototype, '$locals', {
+ configurable: false,
+ enumerable: false,
+ writable: true
+});
+
+/**
* Boolean flag specifying if the document is new.
*
* @api public
@@ -820,6 +850,13 @@
}
const pathType = this.schema.pathType(path);
+
+ // Assume this is a Mongoose document that was copied into a POJO using
+ // `Object.assign()` or `{...doc}`
+ if (utils.isPOJO(val) && val.$__ != null && val._doc != null) {
+ val = val._doc;
+ }
+
if (pathType === 'nested' && val) {
if (utils.isPOJO(val)) {
if (!merge) {
@@ -845,6 +882,11 @@
let schema;
const parts = path.split('.');
+ // Might need to change path for top-level alias
+ if (typeof this.schema.aliases[parts[0]] == 'string') {
+ parts[0] = this.schema.aliases[parts[0]];
+ }
+
if (pathType === 'adhocOrUndefined' && strict) {
// check for roots that are Mixed types
let mixed;
@@ -1014,10 +1056,25 @@
// a single nested doc. That's to make sure we get the correct context.
// Otherwise we would double-call the setter, see gh-7196.
if (this.schema.singleNestedPaths[path] == null) {
- const setterContext = constructing && this.$__.$options.priorDoc ?
- this.$__.$options.priorDoc :
- this;
- val = schema.applySetters(val, setterContext, false, priorVal);
+ // Init the new subdoc to the previous values of the doc, so
+ // getters/setters see the correct current state. We pass the new subdoc
+ // instead of the old subdoc because otherwise side effects in setters
+ // wouldn't work, see gh-7585
+ if (constructing && this.$__.$options.priorDoc) {
+ const priorVal = Object.assign({}, this.$__.$options.priorDoc._doc);
+ delete priorVal[this.schema.options.discriminatorKey];
+ init(this, priorVal, this._doc);
+ }
+
+ val = schema.applySetters(val, this, false, priorVal);
+
+ if (constructing && this.$__.$options.priorDoc) {
+ // Clear init-ed paths afterwards, because those should be paths that
+ // were in the previous doc and should not be in the new one.
+ for (const path of Object.keys(this.$__.activePaths.states.init)) {
+ delete this._doc[path];
+ }
+ }
}
if (!didPopulate && this.$__.populated) {
@@ -1250,6 +1307,9 @@
*
* @param {String} path
* @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for on-the-fly attributes
+ * @param {Object} [options]
+ * @param {Boolean} [options.virtuals=false] Apply virtuals before getting this path
+ * @param {Boolean} [options.getters=true] If false, skip applying getters and just get the raw value
* @api public
*/
@@ -1271,6 +1331,11 @@
return schema.applyGetters(null, this);
}
+ // Might need to change path for top-level alias
+ if (typeof this.schema.aliases[pieces[0]] == 'string') {
+ pieces[0] = this.schema.aliases[pieces[0]];
+ }
+
for (let i = 0, l = pieces.length; i < l; i++) {
if (obj && obj._doc) {
obj = obj._doc;
@@ -1291,7 +1356,7 @@
obj = adhoc.cast(obj);
}
- if (schema != null) {
+ if (schema != null && options.getters !== false) {
obj = schema.applyGetters(obj, this);
} else if (this.schema.nested[path] && options.virtuals) {
// Might need to apply virtuals if this is a nested path
@@ -1389,6 +1454,93 @@
};
/**
+ * Returns the list of paths that have been directly modified. A direct
+ * modified path is a path that you explicitly set, whether via `doc.foo = 'bar'`,
+ * `Object.assign(doc, { foo: 'bar' })`, or `doc.set('foo', 'bar')`.
+ *
+ * A path `a` may be in `modifiedPaths()` but not in `directModifiedPaths()`
+ * because a child of `a` was directly modified.
+ *
+ * ####Example
+ * const schema = new Schema({ foo: String, nested: { bar: String } });
+ * const Model = mongoose.model('Test', schema);
+ * await Model.create({ foo: 'original', nested: { bar: 'original' } });
+ *
+ * const doc = await Model.findOne();
+ * doc.nested.bar = 'modified';
+ * doc.directModifiedPaths(); // ['nested.bar']
+ * doc.modifiedPaths(); // ['nested', 'nested.bar']
+ *
+ * @return {Array}
+ * @api public
+ */
+
+Document.prototype.directModifiedPaths = function() {
+ return Object.keys(this.$__.activePaths.states.modify);
+};
+
+/**
+ * Returns true if the given path is nullish or only contains empty objects.
+ * Useful for determining whether this subdoc will get stripped out by the
+ * [minimize option](/docs/guide.html#minimize).
+ *
+ * ####Example:
+ * const schema = new Schema({ nested: { foo: String } });
+ * const Model = mongoose.model('Test', schema);
+ * const doc = new Model({});
+ * doc.$isEmpty('nested'); // true
+ * doc.nested.$isEmpty(); // true
+ *
+ * doc.nested.foo = 'bar';
+ * doc.$isEmpty('nested'); // false
+ * doc.nested.$isEmpty(); // false
+ *
+ * @api public
+ * @method $isEmpty
+ * @return {Boolean}
+ */
+
+Document.prototype.$isEmpty = function(path) {
+ const isEmptyOptions = {
+ minimize: true,
+ virtuals: false,
+ getters: false,
+ transform: false
+ };
+
+ if (arguments.length > 0) {
+ const v = this.get(path);
+ if (v == null) {
+ return true;
+ }
+ if (typeof v !== 'object') {
+ return false;
+ }
+ if (utils.isPOJO(v)) {
+ return _isEmpty(v);
+ }
+ return Object.keys(v.toObject(isEmptyOptions)).length === 0;
+ }
+
+ return Object.keys(this.toObject(isEmptyOptions)).length === 0;
+};
+
+function _isEmpty(v) {
+ if (v == null) {
+ return true;
+ }
+ if (typeof v !== 'object' || Array.isArray(v)) {
+ return false;
+ }
+ for (const key of Object.keys(v)) {
+ if (!_isEmpty(v[key])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
* Returns the list of paths that have been modified.
*
* @param {Object} [options]
@@ -1725,7 +1877,7 @@
options = null;
}
- return utils.promiseOrCallback(callback, cb => this.$__validate(function(error) {
+ return utils.promiseOrCallback(callback, cb => this.$__validate(options, function(error) {
cb(error);
}), this.constructor.events);
};
@@ -1857,7 +2009,23 @@
* ignore
*/
-Document.prototype.$__validate = function(callback) {
+Document.prototype.$__validate = function(options, callback) {
+ if (typeof options === 'function') {
+ callback = options;
+ options = null;
+ }
+
+ const hasValidateModifiedOnlyOption = options &&
+ (typeof options === 'object') &&
+ ('validateModifiedOnly' in options);
+
+ let shouldValidateModifiedOnly;
+ if (hasValidateModifiedOnlyOption) {
+ shouldValidateModifiedOnly = !!options.validateModifiedOnly;
+ } else {
+ shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
+ }
+
const _this = this;
const _complete = () => {
const err = this.$__.validationError;
@@ -1879,7 +2047,9 @@
// only validate required fields when necessary
const pathDetails = _getPathsToValidate(this);
- const paths = pathDetails[0];
+ const paths = shouldValidateModifiedOnly ?
+ pathDetails[0].filter((path) => this.isModified(path)) :
+ pathDetails[0];
const skipSchemaValidators = pathDetails[1];
if (paths.length === 0) {
@@ -1943,7 +2113,7 @@
_this.invalidate(path, err, undefined, true);
}
--total || complete();
- }, scope, { skipSchemaValidators: skipSchemaValidators[path] });
+ }, scope, { skipSchemaValidators: skipSchemaValidators[path], path: path });
});
};
@@ -1974,16 +2144,29 @@
* @api public
*/
-Document.prototype.validateSync = function(pathsToValidate) {
+Document.prototype.validateSync = function(pathsToValidate, options) {
const _this = this;
+ const hasValidateModifiedOnlyOption = options &&
+ (typeof options === 'object') &&
+ ('validateModifiedOnly' in options);
+
+ let shouldValidateModifiedOnly;
+ if (hasValidateModifiedOnlyOption) {
+ shouldValidateModifiedOnly = !!options.validateModifiedOnly;
+ } else {
+ shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
+ }
+
if (typeof pathsToValidate === 'string') {
pathsToValidate = pathsToValidate.split(' ');
}
// only validate required fields when necessary
const pathDetails = _getPathsToValidate(this);
- let paths = pathDetails[0];
+ let paths = shouldValidateModifiedOnly ?
+ pathDetails[0].filter((path) => this.isModified(path)) :
+ pathDetails[0];
const skipSchemaValidators = pathDetails[1];
if (pathsToValidate && pathsToValidate.length) {
@@ -2015,7 +2198,8 @@
const val = _this.getValue(path);
const err = p.doValidateSync(val, _this, {
- skipSchemaValidators: skipSchemaValidators[path]
+ skipSchemaValidators: skipSchemaValidators[path],
+ path: path
});
if (err && (!p.$isMongooseDocumentArray || err.$isArrayValidatorError)) {
if (p.$isSingleNested &&

lib/helpers/common.js

@@ -4,6 +4,7 @@
* Module dependencies.
*/
+const Decimal128 = require('../types/decimal128');
const ObjectId = require('../types/objectid');
const utils = require('../utils');
@@ -83,5 +84,6 @@
!(val instanceof Date) &&
!(val instanceof ObjectId) &&
(!Array.isArray(val) || val.length > 0) &&
- !(val instanceof Buffer);
+ !(val instanceof Buffer) &&
+ !(val instanceof Decimal128);
}

lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js

@@ -0,0 +1,12 @@
+'use strict';
+
+module.exports = function checkEmbeddedDiscriminatorKeyProjection(userProjection, path, schema, selected, addedPaths) {
+ const userProjectedInPath = Object.keys(userProjection).
+ reduce((cur, key) => cur || key.startsWith(path + '.'), false);
+ const _discriminatorKey = path + '.' + schema.options.discriminatorKey;
+ if (!userProjectedInPath &&
+ addedPaths.length === 1 &&
+ addedPaths[0] === _discriminatorKey) {
+ selected.splice(selected.indexOf(_discriminatorKey), 1);
+ }
+};
\ No newline at end of file

lib/helpers/document/compile.js

@@ -100,6 +100,21 @@
value: true
});
+ const _isEmptyOptions = Object.freeze({
+ minimize: true,
+ virtuals: false,
+ getters: false,
+ transform: false
+ });
+ Object.defineProperty(nested, '$isEmpty', {
+ enumerable: false,
+ configurable: true,
+ writable: false,
+ value: function() {
+ return Object.keys(this.get(path, null, _isEmptyOptions) || {}).length === 0;
+ }
+ });
+
compile(subprops, nested, path, options);
this.$__.getters[path] = nested;
}
@@ -140,7 +155,7 @@
delete result[key];
return;
}
- result[key].enumerable = ['isNew', '$__', 'errors', '_doc'].indexOf(key) === -1;
+ result[key].enumerable = ['isNew', '$__', 'errors', '_doc', '$locals'].indexOf(key) === -1;
});
return result;

lib/helpers/model/applyHooks.js

@@ -14,6 +14,7 @@
*/
applyHooks.middlewareFunctions = [
+ 'deleteOne',
'save',
'validate',
'remove',
@@ -71,7 +72,7 @@
const middleware = schema.s.hooks.
filter(hook => {
- if (hook.name === 'updateOne') {
+ if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
return !!hook['document'];
}
if (hook.name === 'remove') {
@@ -96,6 +97,8 @@
createWrapper('validate', objToDecorate.$__validate, null, kareemOptions);
objToDecorate.$__remove = middleware.
createWrapper('remove', objToDecorate.$__remove, null, kareemOptions);
+ objToDecorate.$__deleteOne = middleware.
+ createWrapper('deleteOne', objToDecorate.$__deleteOne, null, kareemOptions);
objToDecorate.$__init = middleware.
createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);

lib/helpers/model/applyStaticHooks.js

@@ -0,0 +1,58 @@
+'use strict';
+
+const utils = require('../../utils');
+
+module.exports = function applyStaticHooks(model, hooks, statics) {
+ const kareemOptions = {
+ useErrorHandlers: true,
+ numCallbackParams: 1
+ };
+ model.$__insertMany = hooks.createWrapper('insertMany',
+ model.$__insertMany, model, kareemOptions);
+
+ for (const key of Object.keys(statics)) {
+ if (hooks.hasHooks(key)) {
+ const original = model[key];
+
+ model[key] = function() {
+ const numArgs = arguments.length;
+ const lastArg = numArgs > 0 ? arguments[numArgs - 1] : null;
+ const cb = typeof lastArg === 'function' ? lastArg : null;
+ const args = Array.prototype.slice.
+ call(arguments, 0, cb == null ? numArgs : numArgs - 1);
+ // Special case: can't use `Kareem#wrap()` because it doesn't currently
+ // support wrapped functions that return a promise.
+ return utils.promiseOrCallback(cb, callback => {
+ hooks.execPre(key, model, args, function(err) {
+ if (err != null) {
+ return callback(err);
+ }
+
+ let postCalled = 0;
+ const ret = original.apply(model, args.concat(post));
+ if (ret != null && typeof ret.then === 'function') {
+ ret.then(res => post(null, res), err => post(err));
+ }
+
+ function post(error, res) {
+ if (postCalled++ > 0) {
+ return;
+ }
+
+ if (error != null) {
+ return callback(error);
+ }
+
+ hooks.execPost(key, model, [res], function(error) {
+ if (error != null) {
+ return callback(error);
+ }
+ callback(null, res);
+ });
+ }
+ });
+ }, model.events);
+ };
+ }
+ }
+};
\ No newline at end of file

lib/helpers/populate/assignVals.js

@@ -0,0 +1,213 @@
+'use strict';
+
+const MongooseMap = require('../../types/map');
+const assignRawDocsToIdStructure = require('./assignRawDocsToIdStructure');
+const get = require('../get');
+const getVirtual = require('./getVirtual');
+const leanPopulateMap = require('./leanPopulateMap');
+const mpath = require('mpath');
+const sift = require('sift').default;
+const utils = require('../../utils');
+
+module.exports = function assignVals(o) {
+ // Options that aren't explicitly listed in `populateOptions`
+ const userOptions = get(o, 'allOptions.options.options');
+ // `o.options` contains options explicitly listed in `populateOptions`, like
+ // `match` and `limit`.
+ const populateOptions = Object.assign({}, o.options, userOptions, {
+ justOne: o.justOne
+ });
+
+ const originalIds = [].concat(o.rawIds);
+
+ // replace the original ids in our intermediate _ids structure
+ // with the documents found by query
+ assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions);
+
+ // now update the original documents being populated using the
+ // result structure that contains real documents.
+ const docs = o.docs;
+ const rawIds = o.rawIds;
+ const options = o.options;
+ const count = o.count && o.isVirtual;
+
+ function setValue(val) {
+ if (count) {
+ return val;
+ }
+ if (o.justOne === true && Array.isArray(val)) {
+ return valueFilter(val[0], options, populateOptions);
+ } else if (o.justOne === false && !Array.isArray(val)) {
+ return valueFilter([val], options, populateOptions);
+ }
+ return valueFilter(val, options, populateOptions);
+ }
+
+ for (let i = 0; i < docs.length; ++i) {
+ const existingVal = utils.getValue(o.path, docs[i]);
+ if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) {
+ continue;
+ }
+
+ let valueToSet;
+ if (count) {
+ valueToSet = numDocs(rawIds[i]);
+ } else if (Array.isArray(o.match)) {
+ valueToSet = Array.isArray(rawIds[i]) ?
+ sift(o.match[i], rawIds[i]) :
+ sift(o.match[i], [rawIds[i]])[0];
+ } else {
+ valueToSet = rawIds[i];
+ }
+
+ // If we're populating a map, the existing value will be an object, so
+ // we need to transform again
+ const originalSchema = o.originalModel.schema;
+ const isDoc = get(docs[i], '$__', null) != null;
+ let isMap = isDoc ?
+ existingVal instanceof Map :
+ utils.isPOJO(existingVal);
+ // If we pass the first check, also make sure the local field's schematype
+ // is map (re: gh-6460)
+ isMap = isMap && get(originalSchema._getSchema(o.path), '$isSchemaMap');
+ if (!o.isVirtual && isMap) {
+ const _keys = existingVal instanceof Map ?
+ Array.from(existingVal.keys()) :
+ Object.keys(existingVal);
+ valueToSet = valueToSet.reduce((cur, v, i) => {
+ // Avoid casting because that causes infinite recursion
+ cur.$init(_keys[i], v);
+ return cur;
+ }, new MongooseMap({}, docs[i]));
+ }
+
+ if (o.isVirtual && isDoc) {
+ docs[i].populated(o.path, o.justOne ? originalIds[0] : originalIds, o.allOptions);
+ // If virtual populate and doc is already init-ed, need to walk through
+ // the actual doc to set rather than setting `_doc` directly
+ mpath.set(o.path, valueToSet, docs[i], setValue);
+ continue;
+ }
+
+ const parts = o.path.split('.');
+ let cur = docs[i];
+ for (let j = 0; j < parts.length - 1; ++j) {
+ if (cur[parts[j]] == null) {
+ cur[parts[j]] = {};
+ }
+ cur = cur[parts[j]];
+ // If the property in MongoDB is a primitive, we won't be able to populate
+ // the nested path, so skip it. See gh-7545
+ if (typeof cur !== 'object') {
+ return;
+ }
+ }
+ if (docs[i].$__) {
+ docs[i].populated(o.path, o.allIds[i], o.allOptions);
+ }
+
+ // If lean, need to check that each individual virtual respects
+ // `justOne`, because you may have a populated virtual with `justOne`
+ // underneath an array. See gh-6867
+ utils.setValue(o.path, valueToSet, docs[i], setValue, false);
+ }
+};
+
+function numDocs(v) {
+ if (Array.isArray(v)) {
+ return v.length;
+ }
+ return v == null ? 0 : 1;
+}
+
+/*!
+ * 1) Apply backwards compatible find/findOne behavior to sub documents
+ *
+ * find logic:
+ * a) filter out non-documents
+ * b) remove _id from sub docs when user specified
+ *
+ * findOne
+ * a) if no doc found, set to null
+ * b) remove _id from sub docs when user specified
+ *
+ * 2) Remove _ids when specified by users query.
+ *
+ * background:
+ * _ids are left in the query even when user excludes them so
+ * that population mapping can occur.
+ */
+
+function valueFilter(val, assignmentOpts, populateOptions) {
+ if (Array.isArray(val)) {
+ // find logic
+ const ret = [];
+ const numValues = val.length;
+ for (let i = 0; i < numValues; ++i) {
+ const subdoc = val[i];
+ if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null)) {
+ continue;
+ }
+ maybeRemoveId(subdoc, assignmentOpts);
+ ret.push(subdoc);
+ if (assignmentOpts.originalLimit &&
+ ret.length >= assignmentOpts.originalLimit) {
+ break;
+ }
+ }
+
+ // Since we don't want to have to create a new mongoosearray, make sure to
+ // modify the array in place
+ while (val.length > ret.length) {
+ Array.prototype.pop.apply(val, []);
+ }
+ for (let i = 0; i < ret.length; ++i) {
+ val[i] = ret[i];
+ }
+ return val;
+ }
+
+ // findOne
+ if (isPopulatedObject(val)) {
+ maybeRemoveId(val, assignmentOpts);
+ return val;
+ }
+
+ if (populateOptions.justOne === true) {
+ return (val == null ? val : null);
+ }
+ if (populateOptions.justOne === false) {
+ return [];
+ }
+ return val;
+}
+
+/*!
+ * Remove _id from `subdoc` if user specified "lean" query option
+ */
+
+function maybeRemoveId(subdoc, assignmentOpts) {
+ if (assignmentOpts.excludeId) {
+ if (typeof subdoc.setValue === 'function') {
+ delete subdoc._doc._id;
+ } else {
+ delete subdoc._id;
+ }
+ }
+}
+
+/*!
+ * Determine if `obj` is something we can set a populated path to. Can be a
+ * document, a lean document, or an array/map that contains docs.
+ */
+
+function isPopulatedObject(obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ return Array.isArray(obj) ||
+ obj.$isMongooseMap ||
+ obj.$__ != null ||
+ leanPopulateMap.has(obj);
+}
\ No newline at end of file

lib/helpers/query/applyQueryMiddleware.js

@@ -15,6 +15,7 @@
'countDocuments',
'deleteMany',
'deleteOne',
+ 'distinct',
'estimatedDocumentCount',
'find',
'findOne',
@@ -44,7 +45,7 @@
};
const middleware = model.hooks.filter(hook => {
- if (hook.name === 'updateOne') {
+ if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
return hook.query == null || !!hook.query;
}
if (hook.name === 'remove') {
@@ -56,9 +57,12 @@
// `update()` thunk has a different name because `_update` was already taken
Query.prototype._execUpdate = middleware.createWrapper('update',
Query.prototype._execUpdate, null, kareemOptions);
+ // `distinct()` thunk has a different name because `_distinct` was already taken
+ Query.prototype.__distinct = middleware.createWrapper('distinct',
+ Query.prototype.__distinct, null, kareemOptions);
applyQueryMiddleware.middlewareFunctions.
- filter(v => v !== 'update').
+ filter(v => v !== 'update' && v !== 'distinct').
forEach(fn => {
Query.prototype[`_${fn}`] = middleware.createWrapper(fn,
Query.prototype[`_${fn}`], null, kareemOptions);

lib/helpers/query/castUpdate.js

@@ -367,15 +367,31 @@
if (cond && !overwriteOps[op]) {
// Cast values for ops that add data to MongoDB.
// Ensures embedded documents get ObjectIds etc.
- const tmp = schema.cast(Array.isArray(val) ? val : [val]);
- if (Array.isArray(val)) {
- val = tmp;
- } else if (Array.isArray(tmp)) {
- val = tmp[0];
- } else {
- val = tmp;
+ let schemaArrayDepth = 0;
+ let cur = schema;
+ while (cur.$isMongooseArray) {
+ ++schemaArrayDepth;
+ cur = cur.caster;
}
- return val;
+ let arrayDepth = 0;
+ let _val = val;
+ while (Array.isArray(_val)) {
+ ++arrayDepth;
+ _val = _val[0];
+ }
+
+ const additionalNesting = schemaArrayDepth - arrayDepth;
+ while (arrayDepth < schemaArrayDepth) {
+ val = [val];
+ ++arrayDepth;
+ }
+
+ let tmp = schema.cast(Array.isArray(val) ? val : [val]);
+
+ for (let i = 0; i < additionalNesting; ++i) {
+ tmp = tmp[0];
+ }
+ return tmp;
} else if (cond && op === '$set') {
return schema.cast(val);
}

lib/helpers/schema/applyPlugins.js

@@ -0,0 +1,43 @@
+'use strict';
+
+module.exports = function applyPlugins(schema, plugins, options, cacheKey) {
+ if (schema[cacheKey]) {
+ return;
+ }
+ schema[cacheKey] = true;
+
+ if (!options || !options.skipTopLevel) {
+ for (let i = 0; i < plugins.length; ++i) {
+ schema.plugin(plugins[i][0], plugins[i][1]);
+ }
+ }
+
+ options = Object.assign({}, options);
+ delete options.skipTopLevel;
+
+ for (const path of Object.keys(schema.paths)) {
+ const type = schema.paths[path];
+ if (type.schema != null) {
+ applyPlugins(type.schema, plugins, options, cacheKey);
+
+ // Recompile schema because plugins may have changed it, see gh-7572
+ type.caster.prototype.$__setSchema(type.schema);
+ }
+ }
+
+ const discriminators = schema.discriminators;
+ if (discriminators == null) {
+ return;
+ }
+
+ const applyPluginsToDiscriminators = options.applyPluginsToDiscriminators;
+
+ const keys = Object.keys(discriminators);
+ for (let i = 0; i < keys.length; ++i) {
+ const discriminatorKey = keys[i];
+ const discriminatorSchema = discriminators[discriminatorKey];
+
+ applyPlugins(discriminatorSchema, plugins,
+ { skipTopLevel: !applyPluginsToDiscriminators }, cacheKey);
+ }
+};
\ No newline at end of file

lib/helpers/schema/cleanPositionalOperators.js

@@ -8,5 +8,5 @@
module.exports = function cleanPositionalOperators(path) {
return path.
replace(/\.\$(\[[^\]]*\])?\./g, '.0.').
- replace(/\.(\[[^\]]*\])?\$$/, '.0');
+ replace(/\.(\[[^\]]*\])?\$$/g, '.0');
};
\ No newline at end of file

lib/helpers/schema/getPath.js

@@ -0,0 +1,35 @@
+'use strict';
+
+/*!
+ * Behaves like `Schema#path()`, except for it also digs into arrays without
+ * needing to put `.0.`, so `getPath(schema, 'docArr.elProp')` works.
+ */
+
+module.exports = function getPath(schema, path) {
+ let schematype = schema.path(path);
+ if (schematype != null) {
+ return schematype;
+ }
+
+ const pieces = path.split('.');
+ let cur = '';
+ let isArray = false;
+
+ for (const piece of pieces) {
+ if (/^\d+$/.test(piece) && isArray) {
+ continue;
+ }
+ cur = cur.length === 0 ? piece : cur + '.' + piece;
+
+ schematype = schema.path(cur);
+ if (schematype != null && schematype.schema) {
+ schema = schematype.schema;
+ cur = '';
+ if (schematype.$isMongooseDocumentArray) {
+ isArray = true;
+ }
+ }
+ }
+
+ return schematype;
+};
\ No newline at end of file

lib/helpers/update/applyTimestampsToChildren.js

@@ -99,7 +99,7 @@
childPath = childPath.substr(0, firstDot);
update.$set[parentPath.path + '.' + childPath + '.' + updatedAt] = now;
- } else if (path.schema != null && path.schema != schema) {
+ } else if (path.schema != null && path.schema != schema && update.$set[key]) {
timestamps = path.schema.options.timestamps;
createdAt = handleTimestampOption(timestamps, 'createdAt');
updatedAt = handleTimestampOption(timestamps, 'updatedAt');

lib/helpers/update/castArrayFilters.js

@@ -2,6 +2,7 @@
const castFilterPath = require('../query/castFilterPath');
const cleanPositionalOperators = require('../schema/cleanPositionalOperators');
+const getPath = require('../schema/getPath');
const modifiedPaths = require('./modifiedPaths');
module.exports = function castArrayFilters(query) {
@@ -25,7 +26,9 @@
if (firstMatch !== path.lastIndexOf(match)) {
throw new Error(`Path '${path}' contains the same array filter multiple times`);
}
- cur[match.substring(2, match.length - 1)] = path.substr(0, firstMatch - 1);
+ cur[match.substring(2, match.length - 1)] = path.
+ substr(0, firstMatch - 1).
+ replace(/\$\[[^\]]+\]/g, '0');
}
return cur;
}, {});
@@ -53,7 +56,10 @@
// to replace them so we can get the schema path.
filterPath = cleanPositionalOperators(filterPath);
- const schematype = schema.path(filterPath);
+ const schematype = getPath(schema, filterPath);
+ if (schematype == null) {
+ throw new Error(`Could not find path "${filterPath}" in schema`);
+ }
if (typeof filter[firstKey] === 'object') {
filter[firstKey] = castFilterPath(query, schematype, filter[firstKey]);
} else {

lib/index.js

@@ -5,7 +5,12 @@
*/
if (global.MONGOOSE_DRIVER_PATH) {
+ const deprecationWarning = 'The `MONGOOSE_DRIVER_PATH` global property is ' +
+ 'deprecated. Use `mongoose.driver.set()` instead.';
+ const setDriver = require('util').deprecate(function() {
require('./driver').set(require(global.MONGOOSE_DRIVER_PATH));
+ }, deprecationWarning);
+ setDriver();
} else {
require('./driver').set(require('./drivers/node-mongodb-native'));
}
@@ -19,6 +24,7 @@
const Query = require('./query');
const Model = require('./model');
const Document = require('./document');
+const applyPlugins = require('./helpers/schema/applyPlugins');
const get = require('./helpers/get');
const legacyPluralize = require('mongoose-legacy-pluralize');
const utils = require('./utils');
@@ -104,6 +110,18 @@
Mongoose.prototype.STATES = STATES;
/**
+ * The underlying driver this Mongoose instance uses to communicate with
+ * the database. A driver is a Mongoose-specific interface that defines functions
+ * like `find()`.
+ *
+ * @memberOf Mongoose
+ * @property driver
+ * @api public
+ */
+
+Mongoose.prototype.driver = require('./driver');
+
+/**
* Sets mongoose options
*
* ####Example:
@@ -553,42 +571,10 @@
*/
Mongoose.prototype._applyPlugins = function(schema, options) {
- if (schema.$globalPluginsApplied) {
- return;
- }
- schema.$globalPluginsApplied = true;
-
- if (!options || !options.skipTopLevel) {
- for (let i = 0; i < this.plugins.length; ++i) {
- schema.plugin(this.plugins[i][0], this.plugins[i][1]);
- }
- }
-
- for (const path of Object.keys(schema.paths)) {
- const type = schema.paths[path];
- if (type.schema != null) {
- this._applyPlugins(type.schema);
-
- // Recompile schema because plugins may have changed it, see gh-7572
- type.caster.prototype.$__setSchema(type.schema);
- }
- }
-
- const discriminators = schema.discriminators;
- if (discriminators == null) {
- return;
- }
-
- const applyPluginsToDiscriminators = get(this,
+ options = options || {};
+ options.applyPluginsToDiscriminators = get(this,
'options.applyPluginsToDiscriminators', false);
-
- const keys = Object.keys(discriminators);
- for (let i = 0; i < keys.length; ++i) {
- const discriminatorKey = keys[i];
- const discriminatorSchema = discriminators[discriminatorKey];
-
- this._applyPlugins(discriminatorSchema, { skipTopLevel: !applyPluginsToDiscriminators });
- }
+ applyPlugins(schema, this.plugins, options, '$globalPluginsApplied');
};
/**

lib/model.js

@@ -11,7 +11,7 @@
const DivergentArrayError = require('./error').DivergentArrayError;
const Error = require('./error');
const EventEmitter = require('events').EventEmitter;
-const MongooseMap = require('./types/map');
+const MongooseBuffer = require('./types/buffer');
const OverwriteModelError = require('./error').OverwriteModelError;
const PromiseProvider = require('./promise_provider');
const Query = require('./query');
@@ -22,9 +22,10 @@
const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware');
const applyHooks = require('./helpers/model/applyHooks');
const applyMethods = require('./helpers/model/applyMethods');
+const applyStaticHooks = require('./helpers/model/applyStaticHooks');
const applyStatics = require('./helpers/model/applyStatics');
const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
-const assignRawDocsToIdStructure = require('./helpers/populate/assignRawDocsToIdStructure');
+const assignVals = require('./helpers/populate/assignVals');
const castBulkWrite = require('./helpers/model/castBulkWrite');
const discriminator = require('./helpers/model/discriminator');
const getDiscriminatorByValue = require('./queryhelpers').getDiscriminatorByValue;
@@ -37,7 +38,6 @@
const getVirtual = require('./helpers/populate/getVirtual');
const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
const modifiedPaths = require('./helpers/update/modifiedPaths');
-const mpath = require('mpath');
const normalizeRefPath = require('./helpers/populate/normalizeRefPath');
const parallel = require('async/parallel');
const parallelLimit = require('async/parallelLimit');
@@ -407,22 +407,18 @@
* ####Example:
*
* product.sold = Date.now();
- * product.save(function (err, product) {
- * if (err) ..
- * })
- *
- * The callback will receive two parameters
+ * product = await product.save();
*
- * 1. `err` if an error occurred
- * 2. `product` which is the saved `product`
+ * If save is successful, the returned promise will fulfill with the document
+ * saved.
*
- * As an extra measure of flow control, save will return a Promise.
* ####Example:
- * product.save().then(function(product) {
- * ...
- * });
+ *
+ * const newProduct = await product.save();
+ * newProduct === product; // true
*
* @param {Object} [options] options optional options
+ * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session).
* @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead.
* @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
* @param {Number|String} [options.w] set the [write concern](https://docs.mongodb.com/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern)
@@ -430,8 +426,8 @@
* @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern).
* @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names)
* @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`.
- * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session).
* @param {Function} [fn] optional callback
+ * @throws {DocumentNotFoundError} if this [save updates an existing document](api.html#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
* @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise.
* @api public
* @see middleware http://mongoosejs.com/docs/middleware.html
@@ -695,7 +691,7 @@
} else if (value._path && value._atomics) {
// arrays and other custom types (support plugins etc)
handleAtomics(this, where, delta, data, value);
- } else if (value._path && Buffer.isBuffer(value)) {
+ } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) {
// MongooseBuffer
value = value.toObject();
operand(this, where, delta, data, value);
@@ -704,6 +700,7 @@
depopulate: true,
transform: false,
virtuals: false,
+ getters: false,
_isNested: true
});
operand(this, where, delta, data, value);
@@ -910,6 +907,37 @@
Model.prototype.delete = Model.prototype.remove;
+/**
+ * Removes this document from the db. Equivalent to `.remove()`.
+ *
+ * ####Example:
+ * product = await product.deleteOne();
+ * await Product.findById(product._id); // null
+ *
+ * @param {function(err,product)} [fn] optional callback
+ * @return {Promise} Promise
+ * @api public
+ */
+
+Model.prototype.deleteOne = function deleteOne(options, fn) {
+ if (typeof options === 'function') {
+ fn = options;
+ options = undefined;
+ }
+
+ if (!options) {
+ options = {};
+ }
+
+ if (fn) {
+ fn = this.constructor.$wrapCallback(fn);
+ }
+
+ return utils.promiseOrCallback(fn, cb => {
+ this.$__deleteOne(options, cb);
+ }, this.constructor.events);
+};
+
/*!
* ignore
*/
@@ -945,6 +973,12 @@
});
};
+/*!
+ * ignore
+ */
+
+Model.prototype.$__deleteOne = Model.prototype.$__remove;
+
/**
* Returns another Model instance.
*
@@ -1500,8 +1534,14 @@
if (model.baseModelName != null && indexOptions.unique &&
!('partialFilterExpression' in indexOptions) &&
!('sparse' in indexOptions)) {
+
+ const value = (
+ model.schema.discriminatorMapping &&
+ model.schema.discriminatorMapping.value
+ ) || model.modelName;
+
indexOptions.partialFilterExpression = {
- [model.schema.options.discriminatorKey]: model.modelName
+ [model.schema.options.discriminatorKey]: value
};
}
return indexOptions;
@@ -1673,13 +1713,14 @@
*
* ####Example:
*
- * Character.remove({ name: 'Eddard Stark' }, function (err) {});
+ * const res = await Character.remove({ name: 'Eddard Stark' });
+ * res.deletedCount; // Number of documents removed
*
* ####Note:
*
* This method sends a remove command directly to MongoDB, no Mongoose documents
- * are involved. Because no Mongoose documents are involved, _no middleware
- * (hooks) are executed_.
+ * are involved. Because no Mongoose documents are involved, Mongoose does
+ * not execute [document middleware](/docs/middleware.html#types-of-middleware).
*
* @param {Object} conditions
* @param {Function} [callback]
@@ -1895,7 +1936,7 @@
* @param {Function} [callback]
* @return {Query}
* @see field selection #query_Query-select
- * @see lean queries #query_Query-lean
+ * @see lean queries /docs/tutorials/lean.html
* @api public
*/
@@ -1949,7 +1990,7 @@
* @param {Function} [callback]
* @return {Query}
* @see field selection #query_Query-select
- * @see lean queries #query_Query-lean
+ * @see lean queries /docs/tutorials/lean.html
* @api public
*/
@@ -2243,7 +2284,9 @@
* @param {Object} [conditions]
* @param {Object} [update]
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](http://mongoosejs.com/docs/api.html#query_Query-lean).
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback]
* @return {Query}
* @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
@@ -2378,7 +2421,9 @@
* @param {Object|Number|String} id value of `_id` to query by
* @param {Object} [update]
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](http://mongoosejs.com/docs/api.html#query_Query-lean).
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback]
* @return {Query}
* @see Model.findOneAndUpdate #model_Model.findOneAndUpdate
@@ -2464,6 +2509,7 @@
*
* @param {Object} conditions
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2509,6 +2555,7 @@
*
* @param {Object|Number|String} id value of `_id` to query by
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @see Model.findOneAndRemove #model_Model.findOneAndRemove
@@ -2566,6 +2613,9 @@
*
* @param {Object} conditions
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](http://mongoosejs.com/docs/api.html#query_Query-lean).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2648,6 +2698,7 @@
*
* @param {Object} conditions
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
@@ -2711,6 +2762,7 @@
*
* @param {Object|Number|String} id value of `_id` to query by
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @see Model.findOneAndRemove #model_Model.findOneAndRemove
@@ -2790,6 +2842,20 @@
} else {
args = utils.args(arguments);
}
+
+ if (args.length === 2 &&
+ args[0] != null &&
+ args[1] != null &&
+ args[0].session == null &&
+ last.session != null &&
+ last.session.constructor.name === 'ClientSession' &&
+ !this.schema.path('session')) {
+ // Probably means the user is running into the common mistake of trying
+ // to use a spread to specify options, see gh-7535
+ console.warn('WARNING: to pass a `session` to `Model.create()` in ' +
+ 'Mongoose, you **must** pass an array as the first argument. See: ' +
+ 'https://mongoosejs.com/docs/api.html#model_Model.create');
+ }
}
if (cb) {
@@ -3177,10 +3243,12 @@
* ####Examples:
*
* MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
- * MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, function (err, raw) {
- * if (err) return handleError(err);
- * console.log('The raw response from Mongo was ', raw);
- * });
+ *
+ * const res = await MyModel.update({ name: 'Tobi' }, { ferret: true });
+ * res.n; // Number of documents that matched `{ name: 'Tobi' }`
+ * // Number of documents that were changed. If every doc matched already
+ * // had `ferret` set to `true`, `nModified` will be 0.
+ * res.nModified;
*
* ####Valid options:
*
@@ -3206,10 +3274,10 @@
* ####Example:
*
* var query = { name: 'borne' };
- * Model.update(query, { name: 'jason bourne' }, options, callback)
+ * Model.update(query, { name: 'jason bourne' }, options, callback);
*
* // is sent as
- * Model.update(query, { $set: { name: 'jason bourne' }}, options, callback)
+ * Model.update(query, { $set: { name: 'jason bourne' }}, options, function(err, res));
* // if overwrite option is false. If overwrite is true, sent without the $set wrapper.
*
* This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason bourne' }`.
@@ -3242,6 +3310,7 @@
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
* @param {Function} [callback]
* @return {Query}
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3257,6 +3326,11 @@
* **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')`
* and `post('updateMany')` instead.
*
+ * ####Example:
+ * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `updateMany()`
@@ -3264,8 +3338,11 @@
* @param {Object} conditions
* @param {Object} doc
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback]
* @return {Query}
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3280,6 +3357,11 @@
* - MongoDB will update _only_ the first document that matches `criteria` regardless of the value of the `multi` option.
* - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`.
*
+ * ####Example:
+ * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `updateOne()`
@@ -3287,8 +3369,11 @@
* @param {Object} conditions
* @param {Object} doc
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback]
* @return {Query}
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3300,6 +3385,11 @@
* Same as `update()`, except MongoDB replace the existing document with the
* given document (no atomic operators like `$set`).
*
+ * ####Example:
+ * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `replaceOne()`
@@ -3307,6 +3397,8 @@
* @param {Object} conditions
* @param {Object} doc
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -3384,7 +3476,7 @@
* - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions
* - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old
*
- * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the `lean` option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
+ * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the [`lean` option](/docs/tutorials/lean.html); meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
*
* ####Example:
*
@@ -3554,11 +3646,11 @@
* - `near` {Array} x,y point to search for
* - `maxDistance` {Number} the maximum distance from the point near that a result can be
* - `limit` {Number} The maximum number of results to return
- * - `lean` {Boolean} return the raw object instead of the Mongoose Model
+ * - `lean` {Object|Boolean} return the raw object instead of the Mongoose Model
*
* @param {Object} conditions an object that specifies the match condition (required)
* @param {Object} options for the geoSearch, some (near, maxDistance) are required
- * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](http://mongoosejs.com/docs/api.html#query_Query-lean).
+ * @param {Object|Boolean} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html).
* @param {Function} [callback] optional callback
* @return {Promise}
* @see http://docs.mongodb.org/manual/reference/command/geoSearch/
@@ -3689,6 +3781,7 @@
* @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
* @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options).
* @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
+ * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://docs.mongodb.com/manual/tutorial/query-documents/), or a function that returns a filter object.
* @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
* @return {Promise}
* @api public
@@ -3788,15 +3881,7 @@
for (let i = 0; i < len; ++i) {
mod = modelsMap[i];
select = mod.options.select;
-
- if (mod.options.match) {
- match = utils.object.shallowCopy(mod.options.match);
- } else if (get(mod, 'options.options.match')) {
- match = utils.object.shallowCopy(mod.options.options.match);
- delete mod.options.options.match;
- } else {
- match = {};
- }
+ match = _formatMatch(mod.match);
let ids = utils.array.flatten(mod.ids, flatten);
ids = utils.array.unique(ids);
@@ -3813,10 +3898,16 @@
match[foreignField] = { $in: ids };
}
} else {
- match.$or = [];
+ const $or = [];
+ if (Array.isArray(match.$or)) {
+ match.$and = [{ $or: match.$or }, { $or: $or }];
+ delete match.$or;
+ } else {
+ match.$or = $or;
+ }
for (const foreignField of mod.foreignField) {
if (foreignField !== '_id' || !match['_id']) {
- match.$or.push({ [foreignField]: { $in: ids } });
+ $or.push({ [foreignField]: { $in: ids } });
}
}
}
@@ -3838,30 +3929,6 @@
}
}
- // If just setting count, skip everything else
- if (mod.count) {
- mod.model.countDocuments(match, function(err, count) {
- if (err != null) {
- return callback(err);
- }
-
- for (const doc of docs) {
- try {
- if (doc.$__ != null) {
- doc.set(mod.options.path, count);
- } else {
- utils.setValue(mod.options.path, count, doc);
- }
- } catch (err) {
- return callback(err);
- }
- }
-
- callback(null);
- });
- continue;
- }
-
if (mod.options.options && mod.options.options.limit) {
assignmentOpts.originalLimit = mod.options.options.limit;
mod.options.options.limit = mod.options.options.limit * ids.length;
@@ -3871,6 +3938,14 @@
const query = mod.model.find(match, select, mod.options.options);
+ // If using count, still need the `foreignField` so we can match counts
+ // to documents, otherwise we would need a separate `count()` for every doc.
+ if (mod.count) {
+ for (const foreignField of mod.foreignField) {
+ query.select(foreignField);
+ }
+ }
+
// If we're doing virtual populate and projection is inclusive and foreign
// field is not selected, automatically select it because mongoose needs it.
// If projection is exclusive and client explicitly unselected the foreign
@@ -4005,112 +4080,26 @@
isVirtual: mod.isVirtual,
allOptions: mod,
lean: lean,
- virtual: mod.virtual
+ virtual: mod.virtual,
+ count: mod.count,
+ match: mod.match
});
}
}
/*!
- * Assigns documents returned from a population query back
- * to the original document path.
+ * Format `mod.match` given that it may be an array that we need to $or if
+ * the client has multiple docs with match functions
*/
-function assignVals(o) {
- // Options that aren't explicitly listed in `populateOptions`
- const userOptions = get(o, 'allOptions.options.options');
- // `o.options` contains options explicitly listed in `populateOptions`, like
- // `match` and `limit`.
- const populateOptions = Object.assign({}, o.options, userOptions, {
- justOne: o.justOne
- });
-
- const originalIds = [].concat(o.rawIds);
-
- // replace the original ids in our intermediate _ids structure
- // with the documents found by query
- assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions);
-
- // now update the original documents being populated using the
- // result structure that contains real documents.
- const docs = o.docs;
- const rawIds = o.rawIds;
- const options = o.options;
-
- function setValue(val) {
- return valueFilter(val, options, populateOptions);
- }
-
- for (let i = 0; i < docs.length; ++i) {
- const existingVal = utils.getValue(o.path, docs[i]);
- if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) {
- continue;
- }
-
- // If we're populating a map, the existing value will be an object, so
- // we need to transform again
- const originalSchema = o.originalModel.schema;
- let isMap = isModel(docs[i]) ?
- existingVal instanceof Map :
- utils.isPOJO(existingVal);
- // If we pass the first check, also make sure the local field's schematype
- // is map (re: gh-6460)
- isMap = isMap && get(originalSchema._getSchema(o.path), '$isSchemaMap');
- if (!o.isVirtual && isMap) {
- const _keys = existingVal instanceof Map ?
- Array.from(existingVal.keys()) :
- Object.keys(existingVal);
- rawIds[i] = rawIds[i].reduce((cur, v, i) => {
- // Avoid casting because that causes infinite recursion
- cur.$init(_keys[i], v);
- return cur;
- }, new MongooseMap({}, docs[i]));
- }
-
- if (o.isVirtual && docs[i] instanceof Model) {
- docs[i].populated(o.path, o.justOne ? originalIds[0] : originalIds, o.allOptions);
- // If virtual populate and doc is already init-ed, need to walk through
- // the actual doc to set rather than setting `_doc` directly
- mpath.set(o.path, rawIds[i], docs[i], setValue);
- continue;
- }
-
- const parts = o.path.split('.');
- let cur = docs[i];
- for (let j = 0; j < parts.length - 1; ++j) {
- if (cur[parts[j]] == null) {
- cur[parts[j]] = {};
- }
- cur = cur[parts[j]];
- // If the property in MongoDB is a primitive, we won't be able to populate
- // the nested path, so skip it. See gh-7545
- if (typeof cur !== 'object') {
- return;
- }
- }
- if (docs[i].$__) {
- docs[i].populated(o.path, o.allIds[i], o.allOptions);
- }
-
- // If lean, need to check that each individual virtual respects
- // `justOne`, because you may have a populated virtual with `justOne`
- // underneath an array. See gh-6867
- utils.setValue(o.path, rawIds[i], docs[i], function(v) {
- if (o.justOne === true && Array.isArray(v)) {
- return setValue(v[0]);
- } else if (o.justOne === false && !Array.isArray(v)) {
- return setValue([v]);
+function _formatMatch(match) {
+ if (Array.isArray(match)) {
+ if (match.length > 1) {
+ return { $or: [].concat(match.map(m => Object.assign({}, m))) };
}
- return setValue(v);
- }, false);
+ return Object.assign({}, match[0]);
}
-}
-
-/*!
- * Check if obj is a document
- */
-
-function isModel(obj) {
- return get(obj, '$__') != null;
+ return Object.assign({}, match);
}
function getModelsMapForPopulate(model, docs, options) {
@@ -4266,6 +4255,15 @@
const id = String(utils.getValue(foreignField, doc));
options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
+ let match = get(options, 'match', null) ||
+ get(currentOptions, 'match', null) ||
+ get(options, 'virtual.options.options.match', null);
+
+ const hasMatchFunction = typeof match === 'function';
+ if (hasMatchFunction) {
+ match = match.call(doc, doc);
+ }
+
let k = modelNames.length;
while (k--) {
modelName = modelNames[k];
@@ -4304,6 +4302,7 @@
available[modelName] = {
model: Model,
options: currentOptions,
+ match: hasMatchFunction ? [match] : match,
docs: [doc],
ids: [ids],
allIds: [ret],
@@ -4321,6 +4320,9 @@
available[modelName].docs.push(doc);
available[modelName].ids.push(ids);
available[modelName].allIds.push(ret);
+ if (hasMatchFunction) {
+ available[modelName].match.push(match);
+ }
}
}
}
@@ -4464,98 +4466,6 @@
}
/*!
- * 1) Apply backwards compatible find/findOne behavior to sub documents
- *
- * find logic:
- * a) filter out non-documents
- * b) remove _id from sub docs when user specified
- *
- * findOne
- * a) if no doc found, set to null
- * b) remove _id from sub docs when user specified
- *
- * 2) Remove _ids when specified by users query.
- *
- * background:
- * _ids are left in the query even when user excludes them so
- * that population mapping can occur.
- */
-
-function valueFilter(val, assignmentOpts, populateOptions) {
- if (Array.isArray(val)) {
- // find logic
- const ret = [];
- const numValues = val.length;
- for (let i = 0; i < numValues; ++i) {
- const subdoc = val[i];
- if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null)) {
- continue;
- }
- maybeRemoveId(subdoc, assignmentOpts);
- ret.push(subdoc);
- if (assignmentOpts.originalLimit &&
- ret.length >= assignmentOpts.originalLimit) {
- break;
- }
- }
-
- // Since we don't want to have to create a new mongoosearray, make sure to
- // modify the array in place
- while (val.length > ret.length) {
- Array.prototype.pop.apply(val, []);
- }
- for (let i = 0; i < ret.length; ++i) {
- val[i] = ret[i];
- }
- return val;
- }
-
- // findOne
- if (isPopulatedObject(val)) {
- maybeRemoveId(val, assignmentOpts);
- return val;
- }
-
- if (populateOptions.justOne === true) {
- return (val == null ? val : null);
- }
- if (populateOptions.justOne === false) {
- return [];
- }
- return val;
-}
-
-/*!
- * Remove _id from `subdoc` if user specified "lean" query option
- */
-
-function maybeRemoveId(subdoc, assignmentOpts) {
- if (assignmentOpts.excludeId) {
- if (typeof subdoc.setValue === 'function') {
- delete subdoc._doc._id;
- } else {
- delete subdoc._id;
- }
- }
-}
-
-/*!
- * Determine if `obj` is something we can set a populated path to. Can be a
- * document, a lean document, or an array/map that contains docs.
- */
-
-function isPopulatedObject(obj) {
- if (obj == null) {
- return false;
- }
-
- return Array.isArray(obj) ||
- obj.$isMongooseMap ||
- obj.$__ != null ||
- leanPopulateMap.has(obj);
-}
-
-/*!
* Compiler utility.
*
* @param {String|Function} name model name or class extending Model
@@ -4590,6 +4500,17 @@
if (!(this instanceof model)) {
return new model(doc, fields, skipId);
}
+ const discriminatorKey = model.schema.options.discriminatorKey;
+
+ // If discriminator key is set, use the discriminator instead (gh-7586)
+ if (model.discriminators != null &&
+ doc != null &&
+ doc[discriminatorKey] != null &&
+ model.discriminators[doc[discriminatorKey]] != null) {
+ return new model.discriminators[doc[discriminatorKey]](doc, fields, skipId);
+ }
+
+ // Otherwise, just use the top-level model
Model.call(this, doc, fields, skipId);
};
}
@@ -4642,6 +4563,7 @@
applyMethods(model, schema);
applyStatics(model, schema);
applyHooks(model, schema);
+ applyStaticHooks(model, schema.s.hooks, schema.statics);
model.schema = model.prototype.schema;
model.collection = model.prototype.collection;
@@ -4655,13 +4577,6 @@
applyQueryMiddleware(model.Query, model);
applyQueryMethods(model, schema.query);
- const kareemOptions = {
- useErrorHandlers: true,
- numCallbackParams: 1
- };
- model.$__insertMany = model.hooks.createWrapper('insertMany',
- model.$__insertMany, model, kareemOptions);
-
return model;
};

lib/plugins/validateBeforeSave.js

@@ -26,7 +26,13 @@
// Validate
if (shouldValidate) {
- this.validate(function(error) {
+ const hasValidateModifiedOnlyOption = options &&
+ (typeof options === 'object') &&
+ ('validateModifiedOnly' in options);
+ const validateOptions = hasValidateModifiedOnlyOption ?
+ {validateModifiedOnly: options.validateModifiedOnly} :
+ null;
+ this.validate(validateOptions, function(error) {
return _this.schema.s.hooks.execPost('save:error', _this, [ _this], { error: error }, function(error) {
next(error);
});

lib/queryhelpers.js

@@ -4,6 +4,8 @@
* Module dependencies
*/
+const checkEmbeddedDiscriminatorKeyProjection =
+ require('./helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection');
const get = require('./helpers/get');
const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
const utils = require('./utils');
@@ -215,6 +217,7 @@
}
(type.selected ? selected : excluded).push(path);
+ return path;
};
analyzeSchema(schema);
@@ -260,22 +263,34 @@
// avoid recursion
if (stack.indexOf(schema) !== -1) {
- return;
+ return [];
}
stack.push(schema);
+ const addedPaths = [];
schema.eachPath(function(path, type) {
if (prefix) path = prefix + '.' + path;
- analyzePath(path, type);
+ const addedPath = analyzePath(path, type);
+ if (addedPath != null) {
+ addedPaths.push(addedPath);
+ }
- // array of subdocs?
+ // nested schemas
if (type.schema) {
- analyzeSchema(type.schema, path);
+ const _addedPaths = analyzeSchema(type.schema, path);
+
+ // Special case: if discriminator key is the only field that would
+ // be projected in, remove it.
+ if (exclude === false) {
+ checkEmbeddedDiscriminatorKeyProjection(fields, path, type.schema,
+ selected, _addedPaths);
+ }
}
});
stack.pop();
+ return addedPaths;
}
};
@@ -301,7 +316,13 @@
if (error) {
return callback(error);
}
- return callback(null,
- Object.assign({}, res.result, {deletedCount: res.deletedCount }));
+ const mongooseResult = Object.assign({}, res.result);
+ if (get(res, 'result.n', null) != null) {
+ mongooseResult.deletedCount = res.result.n;
+ }
+ if (res.deletedCount != null) {
+ mongooseResult.deletedCount = res.deletedCount;
+ }
+ return callback(null, mongooseResult);
};
};

lib/query.js

@@ -118,7 +118,7 @@
*
* mongoose.Query.use$geoWithin = false;
*
- * MongoDB 2.4 deprecated the use of `$within`, replacing it with `$geoWithin`. Mongoose uses `$geoWithin` by default (which is 100% backward compatible with $within). If you are running an older version of MongoDB, set this flag to `false` so your `within()` queries continue to work.
+ * MongoDB 2.4 deprecated the use of `$within`, replacing it with `$geoWithin`. Mongoose uses `$geoWithin` by default (which is 100% backward compatible with `$within`). If you are running an older version of MongoDB, set this flag to `false` so your `within()` queries continue to work.
*
* @see http://docs.mongodb.org/manual/reference/operator/geoWithin/
* @default true
@@ -374,7 +374,7 @@
*/
/**
- * Specifies a $gt query condition.
+ * Specifies a `$gt` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -395,7 +395,7 @@
*/
/**
- * Specifies a $gte query condition.
+ * Specifies a `$gte` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -409,7 +409,7 @@
*/
/**
- * Specifies a $lt query condition.
+ * Specifies a `$lt` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -423,7 +423,7 @@
*/
/**
- * Specifies a $lte query condition.
+ * Specifies a `$lte` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -437,7 +437,7 @@
*/
/**
- * Specifies a $ne query condition.
+ * Specifies a `$ne` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -451,7 +451,7 @@
*/
/**
- * Specifies an $in query condition.
+ * Specifies an `$in` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -465,7 +465,7 @@
*/
/**
- * Specifies an $nin query condition.
+ * Specifies an `$nin` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -479,21 +479,27 @@
*/
/**
- * Specifies an $all query condition.
+ * Specifies an `$all` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
+ * ####Example:
+ *
+ * MyModel.find().where('pets').all(['dog', 'cat', 'ferret']);
+ * // Equivalent:
+ * MyModel.find().all('pets', ['dog', 'cat', 'ferret']);
+ *
* @see $all http://docs.mongodb.org/manual/reference/operator/all/
* @method all
* @memberOf Query
* @instance
* @param {String} [path]
- * @param {Number} val
+ * @param {Array} val
* @api public
*/
/**
- * Specifies a $size query condition.
+ * Specifies a `$size` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -516,7 +522,7 @@
*/
/**
- * Specifies a $regex query condition.
+ * Specifies a `$regex` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -530,7 +536,7 @@
*/
/**
- * Specifies a $maxDistance query condition.
+ * Specifies a `maxDistance` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
@@ -683,7 +689,7 @@
*/
/**
- * Specifies a $slice projection for an array.
+ * Specifies a `$slice` projection for an array.
*
* ####Example
*
@@ -793,7 +799,7 @@
* @method comment
* @memberOf Query
* @instance
- * @param {Number} val
+ * @param {String} val
* @see comment http://docs.mongodb.org/manual/reference/operator/comment/
* @api public
*/
@@ -840,6 +846,47 @@
*/
/**
+ * Get/set the current projection (AKA fields). Pass `null` to remove the
+ * current projection.
+ *
+ * Unlike `projection()`, the `select()` function modifies the current
+ * projection in place. This function overwrites the existing projection.
+ *
+ * ####Example:
+ *
+ * const q = Model.find();
+ * q.projection(); // null
+ *
+ * q.select('a b');
+ * q.projection(); // { a: 1, b: 1 }
+ *
+ * q.projection({ c: 1 });
+ * q.projection(); // { c: 1 }
+ *
+ * q.projection(null);
+ * q.projection(); // null
+ *
+ *
+ * @method projection
+ * @memberOf Query
+ * @instance
+ * @param {Object|null} arg
+ * @return {Object} the current projection
+ * @api public
+ */
+
+Query.prototype.projection = function(arg) {
+ if (arguments.length === 0) {
+ return this._fields;
+ }
+
+ this._fields = {};
+ this._userProvidedFields = {};
+ this.select(arg);
+ return this._fields;
+};
+
+/**
* Specifies which document fields to include or exclude (also known as the query "projection")
*
* When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](/docs/api.html#schematype_SchemaType-select).
@@ -1210,18 +1257,6 @@
*/
/**
- * Merges another Query or conditions object into this one.
- *
- * When a Query is passed, conditions, field selection and options are merged.
- *
- * @method merge
- * @memberOf Query
- * @instance
- * @param {Query|Object} source
- * @return {Query} this
- */
-
-/**
* Gets query options.
*
* ####Example:
@@ -1262,6 +1297,7 @@
* - [upsert](https://docs.mongodb.com/manual/reference/method/db.collection.update/)
* - [writeConcern](https://docs.mongodb.com/manual/reference/method/db.collection.update/)
* - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): If `timestamps` is set in the schema, set this option to `false` to skip timestamps for that particular update. Has no effect if `timestamps` is not enabled in the schema options.
+ * - omitUndefined: delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
*
* The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`:
*
@@ -1461,7 +1497,7 @@
/**
- * Return an update document with corrected $set operations.
+ * Return an update document with corrected `$set` operations.
*
* @method _updateForExec
* @api private
@@ -1580,7 +1616,9 @@
/**
* Sets the lean option.
*
- * Documents returned from queries with the `lean` option enabled are plain javascript objects, not [MongooseDocuments](#document-js). They have no `save` method, getters/setters or other Mongoose magic applied.
+ * Documents returned from queries with the `lean` option enabled are plain
+ * javascript objects, not [Mongoose Documents](#document-js). They have no
+ * `save` method, getters/setters, virtuals, or other Mongoose features.
*
* ####Example:
*
@@ -1588,11 +1626,12 @@
* new Query().lean(true)
* new Query().lean(false)
*
- * Model.find().lean().exec(function (err, docs) {
- * docs[0] instanceof mongoose.Document // false
- * });
+ * const docs = await Model.find().lean();
+ * docs[0] instanceof mongoose.Document; // false
*
- * This is a [great](https://groups.google.com/forum/#!topic/mongoose-orm/u2_DzDydcnA/discussion) option in high-performance read-only scenarios, especially when combined with [stream](#query_Query-stream).
+ * [Lean is great for high-performance, read-only cases](/docs/tutorials/lean.html),
+ * especially when combined
+ * with [cursors](/docs/queries.html#streaming).
*
* @param {Boolean|Object} bool defaults to true
* @return {Query} this
@@ -2308,6 +2347,27 @@
};
/**
+ * Thunk around findOne()
+ *
+ * @param {Function} [callback]
+ * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/
+ * @api private
+ */
+
+Query.prototype.__distinct = wrapThunk(function __distinct(callback) {
+ this._castConditions();
+
+ if (this.error()) {
+ callback(this.error());
+ return null;
+ }
+
+ // don't pass in the conditions because we already merged them in
+ this._collection.collection.
+ distinct(this._distinct, this._conditions, callback);
+});
+
+/**
* Declares or executes a distict() operation.
*
* Passing a `callback` executes the query.
@@ -2353,16 +2413,16 @@
this.error(new ObjectParameterError(conditions, 'filter', 'distinct'));
}
- if (callback != null) {
- this._castConditions();
-
- if (this.error() != null) {
- callback(this.error());
- return this;
+ if (field != null) {
+ this._distinct = field;
}
+ this.op = 'distinct';
+
+ if (callback != null) {
+ this.__distinct(callback);
}
- return Query.base.distinct.call(this, {}, field, callback);
+ return this;
};
/**
@@ -2748,6 +2808,7 @@
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
* @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](http://mongoosejs.com/docs/api.html#query_Query-lean).
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback] optional params are (error, doc), _unless_ `rawResult` is used, in which case params are (error, writeOpResult)
* @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
* @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
@@ -3070,6 +3131,8 @@
* @param {Object} [options]
* @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](http://mongoosejs.com/docs/api.html#query_Query-lean).
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback] optional params are (error, document)
* @return {Query} this
* @api public
@@ -3571,7 +3634,7 @@
/**
* Declare and/or execute this query as an update() operation.
*
- * _All paths passed that are not $atomic operations will become $set ops._
+ * _All paths passed that are not [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operations will become `$set` ops._
*
* This function triggers the following middleware.
*
@@ -3610,7 +3673,7 @@
*
* q.update({ $set: { name: 'bob' }}).exec(); // executed
*
- * // keys that are not $atomic ops become $set.
+ * // keys that are not [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) ops become `$set`.
* // this executes the same command as the previous example.
* q.update({ name: 'bob' }).exec();
*
@@ -3658,6 +3721,7 @@
* @see Model.update #model_Model.update
* @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
* @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3698,6 +3762,11 @@
* **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')`
* and `post('updateMany')` instead.
*
+ * ####Example:
+ * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `updateMany()`
@@ -3706,11 +3775,13 @@
* @param {Object} [doc] the update command
* @param {Object} [options]
* @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback] optional params are (error, writeOpResult)
* @return {Query} this
* @see Model.update #model_Model.update
* @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
* @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3747,11 +3818,16 @@
* `update()`, except it does not support the `multi` or `overwrite` options.
*
* - MongoDB will update _only_ the first document that matches `criteria` regardless of the value of the `multi` option.
- * - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`.
+ * - Use `replaceOne()` if you want to overwrite an entire document rather than using [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operators like `$set`.
*
* **Note** updateOne will _not_ fire update middleware. Use `pre('updateOne')`
* and `post('updateOne')` instead.
*
+ * ####Example:
+ * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `updateOne()`
@@ -3760,11 +3836,13 @@
* @param {Object} [doc] the update command
* @param {Object} [options]
* @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback] params are (error, writeOpResult)
* @return {Query} this
* @see Model.update #model_Model.update
* @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
* @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3799,11 +3877,16 @@
/**
* Declare and/or execute this query as a replaceOne() operation. Same as
* `update()`, except MongoDB will replace the existing document and will
- * not accept any atomic operators (`$set`, etc.)
+ * not accept any [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operators (`$set`, etc.)
*
* **Note** replaceOne will _not_ fire update middleware. Use `pre('replaceOne')`
* and `post('replaceOne')` instead.
*
+ * ####Example:
+ * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `replaceOne()`
@@ -3811,11 +3894,13 @@
* @param {Object} [criteria]
* @param {Object} [doc] the update command
* @param {Object} [options]
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
* @param {Function} [callback] optional params are (error, writeOpResult)
* @return {Query} this
* @see Model.update #model_Model.update
* @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
* @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -4218,10 +4303,10 @@
* })
*
* Kitten.find().populate({
- * path: 'owner'
- * , select: 'name'
- * , match: { color: 'black' }
- * , options: { sort: { name: -1 }}
+ * path: 'owner',
+ * select: 'name',
+ * match: { color: 'black' },
+ * options: { sort: { name: -1 } }
* }).exec(function (err, kittens) {
* console.log(kittens[0].owner.name) // Zoopa
* })
@@ -4231,13 +4316,21 @@
* console.log(kittens[0].owner.name) // Zoopa
* })
*
- * Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback.
+ * Paths are populated after the query executes and a response is received. A
+ * separate query is then executed for each path specified for population. After
+ * a response for each query has also been returned, the results are passed to
+ * the callback.
*
* @param {Object|String} path either the path to populate or an object specifying all parameters
* @param {Object|String} [select] Field selection for the population query
* @param {Model} [model] The model you wish to use for population. If not specified, populate will look up the model by the name in the Schema's `ref` field.
* @param {Object} [match] Conditions for the population query
* @param {Object} [options] Options for the population query (sort, etc)
+ * @param {String} [options.path=null] The path to populate.
+ * @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
+ * @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options).
+ * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
+ * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://docs.mongodb.com/manual/tutorial/query-documents/), or a function that returns a filter object.
* @see population ./populate.html
* @see Query#select #query_Query-select
* @see Model.populate #model_Model.populate
@@ -4481,13 +4574,16 @@
this.setOptions(opts);
}
+ const options = Object.assign({}, this.options, {
+ projection: this.projection()
+ });
try {
this.cast(this.model);
} catch (err) {
- return (new QueryCursor(this, this.options))._markError(err);
+ return (new QueryCursor(this, options))._markError(err);
}
- return new QueryCursor(this, this.options);
+ return new QueryCursor(this, options);
};
// the rest of these are basically to support older Mongoose syntax with mquery
@@ -4769,7 +4865,7 @@
}
/**
- * Specifies a $polygon condition
+ * Specifies a `$polygon` condition
*
* ####Example
*
@@ -4788,7 +4884,7 @@
*/
/**
- * Specifies a $box condition
+ * Specifies a `$box` condition
*
* ####Example
*
@@ -4825,7 +4921,7 @@
};
/**
- * Specifies a $center or $centerSphere condition.
+ * Specifies a `$center` or `$centerSphere` condition.
*
* ####Example
*
@@ -4868,7 +4964,7 @@
Query.prototype.center = Query.base.circle;
/**
- * _DEPRECATED_ Specifies a $centerSphere condition
+ * _DEPRECATED_ Specifies a `$centerSphere` condition
*
* **Deprecated.** Use [circle](#query_Query-circle) instead.
*

lib/schema/date.js

@@ -216,11 +216,15 @@
if (value) {
let msg = message || MongooseError.messages.Date.min;
- msg = msg.replace(/{MIN}/, (value === Date.now ? 'Date.now()' : this.cast(value).toString()));
+ msg = msg.replace(/{MIN}/, (value === Date.now ? 'Date.now()' : value.toString()));
const _this = this;
this.validators.push({
validator: this.minValidator = function(val) {
- const min = (value === Date.now ? value() : _this.cast(value));
+ let _value = value;
+ if (typeof value === 'function' && value !== Date.now) {
+ _value = _value.call(this);
+ }
+ const min = (_value === Date.now ? _value() : _this.cast(_value));
return val === null || val.valueOf() >= min.valueOf();
},
message: msg,
@@ -272,11 +276,15 @@
if (value) {
let msg = message || MongooseError.messages.Date.max;
- msg = msg.replace(/{MAX}/, (value === Date.now ? 'Date.now()' : this.cast(value).toString()));
+ msg = msg.replace(/{MAX}/, (value === Date.now ? 'Date.now()' : value.toString()));
const _this = this;
this.validators.push({
validator: this.maxValidator = function(val) {
- const max = (value === Date.now ? value() : _this.cast(value));
+ let _value = value;
+ if (typeof _value === 'function' && _value !== Date.now) {
+ _value = _value.call(this);
+ }
+ const max = (_value === Date.now ? _value() : _this.cast(_value));
return val === null || val.valueOf() <= max.valueOf();
},
message: msg,

lib/schema/map.js

@@ -26,7 +26,7 @@
const map = new MongooseMap({}, this.path, doc, this.$__schemaType);
for (const key of Object.keys(val)) {
- map.$init(key, this.$__schemaType.cast(val[key], doc, true));
+ map.$init(key, map.$__schemaType.cast(val[key], doc, true));
}
return map;

lib/schema.js

@@ -55,7 +55,7 @@
* - `minimize`: bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true
* - [read](/docs/guide.html#read): string
* - [writeConcern](/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://docs.mongodb.com/manual/reference/write-concern/)
- * - [shardKey](/docs/guide.html#shardKey): bool - defaults to `null`
+ * - [shardKey](/docs/guide.html#shardKey): object - defaults to `null`
* - [strict](/docs/guide.html#strict): bool - defaults to true
* - [toJSON](/docs/guide.html#toJSON) - object - no default
* - [toObject](/docs/guide.html#toObject) - object - no default
@@ -447,9 +447,33 @@
/**
* Reserved document keys.
*
- * Keys in this object are names that are rejected in schema declarations b/c they conflict with mongoose functionality. Using these key name will throw an error.
- *
- * on, emit, _events, db, get, set, init, isNew, errors, schema, options, modelName, collection, _pres, _posts, toObject
+ * Keys in this object are names that are rejected in schema declarations
+ * because they conflict with Mongoose functionality. If you create a schema
+ * using `new Schema()` with one of these property names, Mongoose will throw
+ * an error.
+ *
+ * - prototype
+ * - emit
+ * - on
+ * - once
+ * - listeners
+ * - removeListener
+ * - collection
+ * - db
+ * - errors
+ * - init
+ * - isModified
+ * - isNew
+ * - get
+ * - modelName
+ * - save
+ * - schema
+ * - toObject
+ * - validate
+ * - remove
+ * - populated
+ * - _pres
+ * - _posts
*
* _NOTE:_ Use of these terms as method names is permitted, but play at your own risk, as they may be existing mongoose document methods you are stomping on.
*

lib/schematype.js

@@ -10,6 +10,7 @@
const get = require('./helpers/get');
const immediate = require('./helpers/immediate');
const schemaTypeSymbol = require('./helpers/symbols').schemaTypeSymbol;
+const util = require('util');
const utils = require('./utils');
const validatorErrorSymbol = require('./helpers/symbols').validatorErrorSymbol;
@@ -597,6 +598,11 @@
}
properties = {message: message, type: type, validator: obj};
}
+
+ if (properties.isAsync) {
+ handleIsAsync();
+ }
+
this.validators.push(properties);
return this;
}
@@ -620,6 +626,15 @@
return this;
};
+/*!
+ * ignore
+ */
+
+const handleIsAsync = util.deprecate(function handleIsAsync() {},
+ 'Mongoose: the `isAsync` option for custom validators is deprecated. Make ' +
+ 'your async validators return a promise instead: ' +
+ 'https://mongoosejs.com/docs/validation.html#async-custom-validators');
+
/**
* Adds a required validator to this SchemaType. The validator gets added
* to the front of this SchemaType's validators array using `unshift()`.
@@ -906,7 +921,7 @@
* @api private
*/
-SchemaType.prototype.doValidate = function(value, fn, scope) {
+SchemaType.prototype.doValidate = function(value, fn, scope, options) {
let err = false;
const path = this.path;
let count = this.validators.length;
@@ -945,7 +960,7 @@
let ok;
const validatorProperties = utils.clone(v);
- validatorProperties.path = path;
+ validatorProperties.path = options && options.path ? options.path : path;
validatorProperties.value = value;
if (validator instanceof RegExp) {
@@ -1041,7 +1056,7 @@
* @api private
*/
-SchemaType.prototype.doValidateSync = function(value, scope) {
+SchemaType.prototype.doValidateSync = function(value, scope, options) {
let err = null;
const path = this.path;
const count = this.validators.length;
@@ -1077,7 +1092,7 @@
const validator = v.validator;
const validatorProperties = utils.clone(v);
- validatorProperties.path = path;
+ validatorProperties.path = options && options.path ? options.path : path;
validatorProperties.value = value;
let ok;

lib/types/buffer.js

@@ -56,50 +56,24 @@
buf.isMongooseBuffer = true;
// make sure these internal props don't show up in Object.keys()
- Object.defineProperties(buf, {
- validators: {
- value: [],
- enumerable: false
- },
- _path: {
- value: path,
- enumerable: false
- },
- _parent: {
- value: doc,
- enumerable: false
- }
- });
-
- if (doc && typeof path === 'string') {
- Object.defineProperty(buf, '_schema', {
- value: doc.schema.path(path)
- });
- }
+ buf[MongooseBuffer.pathSymbol] = path;
+ buf[parentSymbol] = doc;
buf._subtype = 0;
return buf;
}
+const pathSymbol = Symbol.for('mongoose#Buffer#_path');
+const parentSymbol = Symbol.for('mongoose#Buffer#_parent');
+MongooseBuffer.pathSymbol = pathSymbol;
+
/*!
* Inherit from Buffer.
*/
-// MongooseBuffer.prototype = Buffer.alloc(0);
-
MongooseBuffer.mixin = {
/**
- * Parent owner document
- *
- * @api private
- * @property _parent
- * @receiver MongooseBuffer
- */
-
- _parent: undefined,
-
- /**
* Default subtype for the Binary representing this Buffer
*
* @api private
@@ -118,10 +92,10 @@
*/
_markModified: function() {
- const parent = this._parent;
+ const parent = this[parentSymbol];
if (parent) {
- parent.markModified(this._path);
+ parent.markModified(this[MongooseBuffer.pathSymbol]);
}
return this;
},
@@ -219,7 +193,7 @@
const subtype = typeof options === 'number'
? options
: (this._subtype || 0);
- return new Binary(this, subtype);
+ return new Binary(Buffer.from(this), subtype);
};
/**

lib/types/subdocument.js

@@ -98,6 +98,15 @@
}
};
+/**
+ * Marks a path as valid, removing existing validation errors.
+ *
+ * @param {String} path the field to mark as valid
+ * @api private
+ * @method $markValid
+ * @receiver EmbeddedDocument
+ */
+
Subdocument.prototype.$markValid = function(path) {
Document.prototype.$markValid.call(this, path);
if (this.$parent && this.$basePath) {

package.json

@@ -1,7 +1,7 @@
{
"name": "mongoose",
"description": "Mongoose MongoDB ODM",
- "version": "5.4.19",
+ "version": "5.5.0",
"author": "Guillermo Rauch <guillermo@learnboost.com>",
"keywords": [
"mongodb",
@@ -20,17 +20,18 @@
"license": "MIT",
"dependencies": {
"async": "2.6.1",
- "bson": "~1.1.0",
+ "bson": "~1.1.1",
"kareem": "2.3.0",
- "mongodb": "3.1.13",
- "mongodb-core": "3.1.11",
+ "mongodb": "3.2.2",
+ "mongodb-core": "3.2.2",
"mongoose-legacy-pluralize": "1.0.2",
"mpath": "0.5.1",
"mquery": "3.2.0",
"ms": "2.1.1",
"regexp-clone": "0.0.1",
"safe-buffer": "5.1.2",
- "sliced": "1.0.1"
+ "sliced": "1.0.1",
+ "sift": "7.0.1"
},
"devDependencies": {
"acorn": "5.7.3",
@@ -57,6 +58,7 @@
"mongoose-long": "0.2.1",
"node-static": "0.7.10",
"nyc": "11.8.0",
+ "object-sizeof": "1.3.0",
"power-assert": "1.4.1",
"promise-debug": "0.1.1",
"q": "1.5.1",

README.md

@@ -66,7 +66,7 @@
```js
const mongoose = require('mongoose');
-mongoose.connect('mongodb://localhost/my_database');
+mongoose.connect('mongodb://localhost/my_database', {useNewUrlParser: true});
```
Once connected, the `open` event is fired on the `Connection` instance. If you're using `mongoose.connect`, the `Connection` is `mongoose.connection`. Otherwise, `mongoose.createConnection` return value is a `Connection`.

.travis.yml

@@ -1,6 +1,6 @@
language: node_js
sudo: false
-node_js: [10, 9, 8, 7, 6, 5, 4]
+node_js: [11, 10, 9, 8, 7, 6, 5, 4]
install:
- travis_retry npm install
before_script: