Skip to content

Commit b2af0fe

Browse files
authored
Merge pull request #12587 from Automattic/6.7
6.7
2 parents 61e96d7 + a20f7bf commit b2af0fe

31 files changed

Lines changed: 1299 additions & 99 deletions

lib/aggregate.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ Aggregate.prototype.model = function(model) {
111111
this._model = model;
112112
if (model.schema != null) {
113113
if (this.options.readPreference == null &&
114-
model.schema.options.read != null) {
114+
model.schema.options.read != null) {
115115
this.options.readPreference = model.schema.options.read;
116116
}
117117
if (this.options.collation == null &&
118-
model.schema.options.collation != null) {
118+
model.schema.options.collation != null) {
119119
this.options.collation = model.schema.options.collation;
120120
}
121121
}
@@ -158,7 +158,7 @@ Aggregate.prototype.append = function() {
158158
* Requires MongoDB v3.4+ to work
159159
*
160160
* #### Example:
161-
*
161+
*
162162
* // adding new fields based on existing fields
163163
* aggregate.addFields({
164164
* newField: '$b.nested'
@@ -328,6 +328,28 @@ Aggregate.prototype.project = function(arg) {
328328
* @api public
329329
*/
330330

331+
/**
332+
* Appends a new $fill operator to this aggregate pipeline.
333+
*
334+
* #### Example:
335+
*
336+
* aggregate.fill({
337+
* output: {
338+
* bootsSold: { value: 0 },
339+
* sandalsSold: { value: 0 },
340+
* sneakersSold: { value: 0 }
341+
* }
342+
* });
343+
*
344+
* @see $fill https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/
345+
* @method fill
346+
* @memberOf Aggregate
347+
* @instance
348+
* @param {Object} arg $fill operator contents
349+
* @return {Aggregate}
350+
* @api public
351+
*/
352+
331353
/**
332354
* Appends a new $geoNear operator to this aggregate pipeline.
333355
*
@@ -366,7 +388,7 @@ Aggregate.prototype.near = function(arg) {
366388
* define methods
367389
*/
368390

369-
'group match skip limit out densify'.split(' ').forEach(function($operator) {
391+
'group match skip limit out densify fill'.split(' ').forEach(function($operator) {
370392
Aggregate.prototype[$operator] = function(arg) {
371393
const op = {};
372394
op['$' + $operator] = arg;
@@ -702,7 +724,7 @@ Aggregate.prototype.readConcern = function(level) {
702724
Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) {
703725
if (arguments.length === 3) {
704726
if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) ||
705-
(typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
727+
(typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
706728
throw new Error('If thenExpr or elseExpr is string, it must be either $$DESCEND, $$PRUNE or $$KEEP');
707729
}
708730

@@ -1099,9 +1121,7 @@ Aggregate.prototype.catch = function(reject) {
10991121

11001122
if (Symbol.asyncIterator != null) {
11011123
Aggregate.prototype[Symbol.asyncIterator] = function() {
1102-
return this.cursor({ useMongooseAggCursor: true }).
1103-
transformNull().
1104-
_transformForAsyncIterator();
1124+
return this.cursor({ useMongooseAggCursor: true }).transformNull()._transformForAsyncIterator();
11051125
};
11061126
}
11071127

lib/document.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,48 @@ Document.prototype.$session = function $session(session) {
948948
return session;
949949
};
950950

951+
/**
952+
* Getter/setter around whether this document will apply timestamps by
953+
* default when using `save()` and `bulkSave()`.
954+
*
955+
* #### Example:
956+
*
957+
* const TestModel = mongoose.model('Test', new Schema({ name: String }, { timestamps: true }));
958+
* const doc = new TestModel({ name: 'John Smith' });
959+
*
960+
* doc.$timestamps(); // true
961+
*
962+
* doc.$timestamps(false);
963+
* await doc.save(); // Does **not** apply timestamps
964+
*
965+
* @param {Boolean} [value] overwrite the current session
966+
* @return {Document|boolean|undefined} When used as a getter (no argument), a boolean will be returned indicating the timestamps option state or if unset "undefined" will be used, otherwise will return "this"
967+
* @method $timestamps
968+
* @api public
969+
* @memberOf Document
970+
*/
971+
972+
Document.prototype.$timestamps = function $timestamps(value) {
973+
if (arguments.length === 0) {
974+
if (this.$__.timestamps != null) {
975+
return this.$__.timestamps;
976+
}
977+
978+
if (this.$__schema) {
979+
return this.$__schema.options.timestamps;
980+
}
981+
982+
return undefined;
983+
}
984+
985+
const currentValue = this.$timestamps();
986+
if (value !== currentValue) {
987+
this.$__.timestamps = value;
988+
}
989+
990+
return this;
991+
};
992+
951993
/**
952994
* Overwrite all values in this document with the values of `obj`, except
953995
* for immutable properties. Behaves similarly to `set()`, except for it

lib/error/setOptionError.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*!
2+
* Module requirements
3+
*/
4+
5+
'use strict';
6+
7+
const MongooseError = require('./mongooseError');
8+
const util = require('util');
9+
const combinePathErrors = require('../helpers/error/combinePathErrors');
10+
11+
class SetOptionError extends MongooseError {
12+
/**
13+
* Mongoose.set Error
14+
*
15+
* @api private
16+
* @inherits MongooseError
17+
*/
18+
constructor() {
19+
super('');
20+
21+
this.errors = {};
22+
}
23+
24+
/**
25+
* Console.log helper
26+
*/
27+
toString() {
28+
return combinePathErrors(this);
29+
}
30+
31+
/**
32+
* inspect helper
33+
* @api private
34+
*/
35+
inspect() {
36+
return Object.assign(new Error(this.message), this);
37+
}
38+
39+
/**
40+
* add message
41+
* @param {String} key
42+
* @param {String|Error} error
43+
* @api private
44+
*/
45+
addError(key, error) {
46+
if (error instanceof SetOptionError) {
47+
const { errors } = error;
48+
for (const optionKey of Object.keys(errors)) {
49+
this.addError(optionKey, errors[optionKey]);
50+
}
51+
52+
return;
53+
}
54+
55+
this.errors[key] = error;
56+
this.message = combinePathErrors(this);
57+
}
58+
}
59+
60+
61+
if (util.inspect.custom) {
62+
// Avoid Node deprecation warning DEP0079
63+
SetOptionError.prototype[util.inspect.custom] = SetOptionError.prototype.inspect;
64+
}
65+
66+
/**
67+
* Helper for JSON.stringify
68+
* Ensure `name` and `message` show up in toJSON output re: gh-9847
69+
* @api private
70+
*/
71+
Object.defineProperty(SetOptionError.prototype, 'toJSON', {
72+
enumerable: false,
73+
writable: false,
74+
configurable: true,
75+
value: function() {
76+
return Object.assign({}, this, { name: this.name, message: this.message });
77+
}
78+
});
79+
80+
81+
Object.defineProperty(SetOptionError.prototype, 'name', {
82+
value: 'SetOptionError'
83+
});
84+
85+
class SetOptionInnerError extends MongooseError {
86+
/**
87+
* Error for the "errors" array in "SetOptionError" with consistent message
88+
* @param {String} key
89+
*/
90+
constructor(key) {
91+
super(`"${key}" is not a valid option to set`);
92+
}
93+
}
94+
95+
SetOptionError.SetOptionInnerError = SetOptionInnerError;
96+
97+
/*!
98+
* Module exports
99+
*/
100+
101+
module.exports = SetOptionError;

lib/error/validation.js

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const MongooseError = require('./mongooseError');
88
const getConstructorName = require('../helpers/getConstructorName');
99
const util = require('util');
10+
const combinePathErrors = require('../helpers/error/combinePathErrors');
1011

1112
class ValidationError extends MongooseError {
1213
/**
@@ -38,7 +39,7 @@ class ValidationError extends MongooseError {
3839
* Console.log helper
3940
*/
4041
toString() {
41-
return this.name + ': ' + _generateMessage(this);
42+
return this.name + ': ' + combinePathErrors(this);
4243
}
4344

4445
/**
@@ -66,7 +67,7 @@ class ValidationError extends MongooseError {
6667
}
6768

6869
this.errors[path] = error;
69-
this.message = this._message + ': ' + _generateMessage(this);
70+
this.message = this._message + ': ' + combinePathErrors(this);
7071
}
7172
}
7273

@@ -95,27 +96,6 @@ Object.defineProperty(ValidationError.prototype, 'name', {
9596
value: 'ValidationError'
9697
});
9798

98-
/*!
99-
* ignore
100-
*/
101-
102-
function _generateMessage(err) {
103-
const keys = Object.keys(err.errors || {});
104-
const len = keys.length;
105-
const msgs = [];
106-
let key;
107-
108-
for (let i = 0; i < len; ++i) {
109-
key = keys[i];
110-
if (err === err.errors[key]) {
111-
continue;
112-
}
113-
msgs.push(key + ': ' + err.errors[key].message);
114-
}
115-
116-
return msgs.join(', ');
117-
}
118-
11999
/*!
120100
* Module exports
121101
*/
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
/*!
4+
* ignore
5+
*/
6+
7+
module.exports = function combinePathErrors(err) {
8+
const keys = Object.keys(err.errors || {});
9+
const len = keys.length;
10+
const msgs = [];
11+
let key;
12+
13+
for (let i = 0; i < len; ++i) {
14+
key = keys[i];
15+
if (err === err.errors[key]) {
16+
continue;
17+
}
18+
msgs.push(key + ': ' + err.errors[key].message);
19+
}
20+
21+
return msgs.join(', ');
22+
};

lib/helpers/model/discriminator.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
1919
* ignore
2020
*/
2121

22-
module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) {
22+
module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins, mergeHooks) {
2323
if (!(schema && schema.instanceOfSchema)) {
2424
throw new Error('You must pass a valid discriminator Schema');
2525
}
2626

27+
mergeHooks = mergeHooks == null ? true : mergeHooks;
28+
2729
if (model.schema.discriminatorMapping &&
2830
!model.schema.discriminatorMapping.isRoot) {
2931
throw new Error('Discriminator "' + name +
@@ -32,7 +34,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
3234

3335
if (applyPlugins) {
3436
const applyPluginsToDiscriminators = get(model.base,
35-
'options.applyPluginsToDiscriminators', false);
37+
'options.applyPluginsToDiscriminators', false) || !mergeHooks;
3638
// Even if `applyPluginsToDiscriminators` isn't set, we should still apply
3739
// global plugins to schemas embedded in the discriminator schema (gh-7370)
3840
model.base._applyPlugins(schema, {
@@ -179,7 +181,9 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
179181
schema.options._id = _id;
180182
}
181183
schema.options.id = id;
182-
schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
184+
if (mergeHooks) {
185+
schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
186+
}
183187

184188
schema.plugins = Array.prototype.slice.call(baseSchema.plugins);
185189
schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);

0 commit comments

Comments
 (0)