Skip to content

Commit

Permalink
Merge branch 'master' into 8.4
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Apr 27, 2024
2 parents d509ae8 + 5137eeb commit 6ca274d
Show file tree
Hide file tree
Showing 26 changed files with 553 additions and 97 deletions.
33 changes: 26 additions & 7 deletions CHANGELOG.md
@@ -1,3 +1,23 @@
8.3.2 / 2024-04-16
==================
* fix(populate): avoid match function filtering out null values in populate result #14518 #14494
* types(query): make FilterQuery props resolve to any for generics support #14510 #14473 #14459
* types(DocumentArray): pass DocType generic to Document for correct toJSON() and toObject() return types #14526 #14469
* types(models): fix incorrect bulk write options #14513 [emiljanitzek](https://github.com/emiljanitzek)
* docs: add documentation for calling schema.post() with async function #14514 #14305

7.6.11 / 2024-04-11
===================
* fix(populate): avoid match function filtering out null values in populate result #14518
* fix(schema): support setting discriminator options in Schema.prototype.discriminator() #14493 #14448
* fix(schema): deduplicate idGetter so creating multiple models with same schema doesn't result in multiple id getters #14492 #14457

6.12.8 / 2024-04-10
===================
* fix(document): handle virtuals that are stored as objects but getter returns string with toJSON #14468 #14446
* fix(schematype): consistently set wasPopulated to object with `value` property rather than boolean #14418
* docs(model): add extra note about lean option for insertMany() skipping casting #14415 #14376

8.3.1 / 2024-04-08
==================
* fix(document): make update minimization unset property rather than setting to null #14504 #14445
Expand Down Expand Up @@ -68,7 +88,6 @@
* docs(connections): add note about using asPromise() with createConnection() for error handling #14364 #14266
* docs(model+query+findoneandupdate): add more details about overwriteDiscriminatorKey option to docs #14264 #14246

<<<<<<< HEAD
8.2.0 / 2024-02-22
==================
* feat(model): add recompileSchema() function to models to allow applying schema changes after compiling #14306 #14296
Expand Down Expand Up @@ -102,6 +121,11 @@
* types(query): add back context and setDefaultsOnInsert as Mongoose-specific query options #14284 #14282
* types(query): add missing runValidators back to MongooseQueryOptions #14278 #14275

6.12.6 / 2024-01-22
===================
* fix(collection): correctly handle buffer timeouts with find() #14277
* fix(document): allow calling push() with different $position arguments #14254

8.1.0 / 2024-01-16
==================
* feat: upgrade MongoDB driver -> 6.3.0 #14241 #14189 #14108 #14104
Expand All @@ -126,12 +150,6 @@
* docs: update TLS/SSL guide for Mongoose v8 - MongoDB v6 driver deprecations #14170 [andylwelch](https://github.com/andylwelch)
* docs: update findOneAndUpdate tutorial to use includeResultMetadata #14208 #14207
* docs: clarify disabling _id on subdocs #14195 #14194
=======
6.12.6 / 2024-01-22
===================
* fix(collection): correctly handle buffer timeouts with find() #14277
* fix(document): allow calling push() with different $position arguments #14254
>>>>>>> 7.x

7.6.8 / 2024-01-08
==================
Expand Down Expand Up @@ -432,6 +450,7 @@
* perf: speed up mapOfSubdocs benchmark by 4x by avoiding unnecessary O(n^2) loop in getPathsToValidate() #13614

7.3.4 / 2023-07-12
==================
* chore: release 7.4.4 to overwrite accidental publish of 5.13.20 to latest tag

6.11.3 / 2023-07-11
Expand Down
25 changes: 21 additions & 4 deletions docs/middleware.md
Expand Up @@ -151,7 +151,7 @@ schema.pre('save', function() {
then(() => doMoreStuff());
});

// Or, in Node.js >= 7.6.0:
// Or, using async functions
schema.pre('save', async function() {
await doStuff();
await doMoreStuff();
Expand Down Expand Up @@ -250,9 +250,7 @@ schema.post('deleteOne', function(doc) {

<h2 id="post-async"><a href="#post-async">Asynchronous Post Hooks</a></h2>

If your post hook function takes at least 2 parameters, mongoose will
assume the second parameter is a `next()` function that you will call to
trigger the next middleware in the sequence.
If your post hook function takes at least 2 parameters, mongoose will assume the second parameter is a `next()` function that you will call to trigger the next middleware in the sequence.

```javascript
// Takes 2 parameters: this is an asynchronous post hook
Expand All @@ -271,6 +269,25 @@ schema.post('save', function(doc, next) {
});
```

You can also pass an async function to `post()`.
If you pass an async function that takes at least 2 parameters, you are still responsible for calling `next()`.
However, you can also pass in an async function that takes less than 2 parameters, and Mongoose will wait for the promise to resolve.

```javascript
schema.post('save', async function(doc) {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('post1');
// If less than 2 parameters, no need to call `next()`
});

schema.post('save', async function(doc, next) {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('post1');
// If there's a `next` parameter, you need to call `next()`.
next();
});
```

<h2 id="defining"><a href="#defining">Define Middleware Before Compiling Models</a></h2>

Calling `pre()` or `post()` after [compiling a model](models.html#compiling)
Expand Down
15 changes: 12 additions & 3 deletions lib/document.js
Expand Up @@ -1053,7 +1053,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
if (path.$__isNested) {
path = path.toObject();
} else {
path = path._doc;
// This ternary is to support gh-7898 (copying virtuals if same schema)
// while not breaking gh-10819, which for some reason breaks if we use toObject()
path = path.$__schema === this.$__schema
? applyVirtuals(path, { ...path._doc })
: path._doc;
}
}
if (path == null) {
Expand Down Expand Up @@ -4087,6 +4091,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
? toObjectOptions.aliases
: true;

options = options || {};
let virtualsToApply = null;
if (Array.isArray(options.virtuals)) {
virtualsToApply = new Set(options.virtuals);
Expand All @@ -4103,7 +4108,6 @@ function applyVirtuals(self, json, options, toObjectOptions) {
return json;
}

options = options || {};
for (i = 0; i < numPaths; ++i) {
path = paths[i];

Expand Down Expand Up @@ -4184,7 +4188,12 @@ function applyGetters(self, json, options) {
for (let ii = 0; ii < plen; ++ii) {
part = parts[ii];
v = cur[part];
if (ii === last) {
// If we've reached a non-object part of the branch, continuing would
// cause "Cannot create property 'foo' on string 'bar'" error.
// Necessary for mongoose-intl plugin re: gh-14446
if (branch != null && typeof branch !== 'object') {
break;
} else if (ii === last) {
const val = self.$get(path);
branch[part] = clone(val, options);
if (Array.isArray(branch[part]) && schema.paths[path].$embeddedSchemaType) {
Expand Down
7 changes: 5 additions & 2 deletions lib/helpers/discriminator/applyEmbeddedDiscriminators.js
Expand Up @@ -20,12 +20,15 @@ function applyEmbeddedDiscriminators(schema, seen = new WeakSet(), overwriteExis
continue;
}
for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) {
const discriminatorSchema = schemaType.schema._applyDiscriminators.get(discriminatorKey);
const {
schema: discriminatorSchema,
options
} = schemaType.schema._applyDiscriminators.get(discriminatorKey);
applyEmbeddedDiscriminators(discriminatorSchema, seen);
schemaType.discriminator(
discriminatorKey,
discriminatorSchema,
overwriteExisting ? { overwriteExisting: true } : null
overwriteExisting ? { ...options, overwriteExisting: true } : options
);
}
schemaType._appliedDiscriminators = true;
Expand Down
4 changes: 2 additions & 2 deletions lib/helpers/populate/assignVals.js
Expand Up @@ -101,8 +101,8 @@ module.exports = function assignVals(o) {
valueToSet = numDocs(rawIds[i]);
} else if (Array.isArray(o.match)) {
valueToSet = Array.isArray(rawIds[i]) ?
rawIds[i].filter(sift(o.match[i])) :
[rawIds[i]].filter(sift(o.match[i]))[0];
rawIds[i].filter(v => v == null || sift(o.match[i])(v)) :
[rawIds[i]].filter(v => v == null || sift(o.match[i])(v))[0];
} else {
valueToSet = rawIds[i];
}
Expand Down
16 changes: 1 addition & 15 deletions lib/helpers/populate/getModelsMapForPopulate.js
Expand Up @@ -410,26 +410,12 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
justOne = options.justOne;
}

modelNames = virtual._getModelNamesForPopulate(doc);
if (virtual.options.refPath) {
modelNames =
modelNamesFromRefPath(virtual.options.refPath, doc, options.path);
justOne = !!virtual.options.justOne;
data.isRefPath = true;
} else if (virtual.options.ref) {
let normalizedRef;
if (typeof virtual.options.ref === 'function' && !virtual.options.ref[modelSymbol]) {
normalizedRef = virtual.options.ref.call(doc, doc);
} else {
normalizedRef = virtual.options.ref;
}
justOne = !!virtual.options.justOne;
// When referencing nested arrays, the ref should be an Array
// of modelNames.
if (Array.isArray(normalizedRef)) {
modelNames = normalizedRef;
} else {
modelNames = [normalizedRef];
}
}

data.isVirtual = true;
Expand Down
2 changes: 1 addition & 1 deletion lib/model.js
Expand Up @@ -4774,7 +4774,7 @@ function _assign(model, vals, mod, assignmentOpts) {
}
// flag each as result of population
if (!lean) {
val.$__.wasPopulated = val.$__.wasPopulated || true;
val.$__.wasPopulated = val.$__.wasPopulated || { value: _val };
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion lib/mongoose.js
Expand Up @@ -637,7 +637,11 @@ Mongoose.prototype._model = function(name, schema, collection, options) {

if (schema._applyDiscriminators != null) {
for (const disc of schema._applyDiscriminators.keys()) {
model.discriminator(disc, schema._applyDiscriminators.get(disc));
const {
schema: discriminatorSchema,
options
} = schema._applyDiscriminators.get(disc);
model.discriminator(disc, discriminatorSchema, options);
}
}

Expand Down
34 changes: 28 additions & 6 deletions lib/schema.js
Expand Up @@ -626,12 +626,18 @@ Schema.prototype.defaultOptions = function(options) {
*
* @param {String} name the name of the discriminator
* @param {Schema} schema the discriminated Schema
* @param {Object} [options] discriminator options
* @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
* @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
* @param {Boolean} [options.overwriteModels=false] by default, Mongoose does not allow you to define a discriminator with the same name as another discriminator. Set this to allow overwriting discriminators with the same name.
* @param {Boolean} [options.mergeHooks=true] By default, Mongoose merges the base schema's hooks with the discriminator schema's hooks. Set this option to `false` to make Mongoose use the discriminator schema's hooks instead.
* @param {Boolean} [options.mergePlugins=true] By default, Mongoose merges the base schema's plugins with the discriminator schema's plugins. Set this option to `false` to make Mongoose use the discriminator schema's plugins instead.
* @return {Schema} the Schema instance
* @api public
*/
Schema.prototype.discriminator = function(name, schema) {
Schema.prototype.discriminator = function(name, schema, options) {
this._applyDiscriminators = this._applyDiscriminators || new Map();
this._applyDiscriminators.set(name, schema);
this._applyDiscriminators.set(name, { schema, options });

return this;
};
Expand Down Expand Up @@ -2291,7 +2297,10 @@ Schema.prototype.virtual = function(name, options) {
throw new Error('Reference virtuals require `foreignField` option');
}

this.pre('init', function virtualPreInit(obj) {
const virtual = this.virtual(name);
virtual.options = options;

this.pre('init', function virtualPreInit(obj, opts) {
if (mpath.has(name, obj)) {
const _v = mpath.get(name, obj);
if (!this.$$populatedVirtuals) {
Expand All @@ -2308,13 +2317,26 @@ Schema.prototype.virtual = function(name, options) {
_v == null ? [] : [_v];
}

if (opts?.hydratedPopulatedDocs && !options.count) {
const modelNames = virtual._getModelNamesForPopulate(this);
const populatedVal = this.$$populatedVirtuals[name];
if (!Array.isArray(populatedVal) && !populatedVal.$__ && modelNames?.length === 1) {
const PopulateModel = this.db.model(modelNames[0]);
this.$$populatedVirtuals[name] = PopulateModel.hydrate(populatedVal);
} else if (Array.isArray(populatedVal) && modelNames?.length === 1) {
const PopulateModel = this.db.model(modelNames[0]);
for (let i = 0; i < populatedVal.length; ++i) {
if (!populatedVal[i].$__) {
populatedVal[i] = PopulateModel.hydrate(populatedVal[i]);
}
}
}
}

mpath.unset(name, obj);
}
});

const virtual = this.virtual(name);
virtual.options = options;

virtual.
set(function(v) {
if (!this.$$populatedVirtuals) {
Expand Down
16 changes: 3 additions & 13 deletions lib/schema/documentArray.js
Expand Up @@ -447,19 +447,9 @@ SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {

const Constructor = getConstructor(this.casterConstructor, rawArray[i]);

// Check if the document has a different schema (re gh-3701)
if (rawArray[i].$__ != null && !(rawArray[i] instanceof Constructor)) {
const spreadDoc = handleSpreadDoc(rawArray[i], true);
if (rawArray[i] !== spreadDoc) {
rawArray[i] = spreadDoc;
} else {
rawArray[i] = rawArray[i].toObject({
transform: false,
// Special case: if different model, but same schema, apply virtuals
// re: gh-7898
virtuals: rawArray[i].schema === Constructor.schema
});
}
const spreadDoc = handleSpreadDoc(rawArray[i], true);
if (rawArray[i] !== spreadDoc) {
rawArray[i] = spreadDoc;
}

if (rawArray[i] instanceof Subdocument) {
Expand Down
4 changes: 2 additions & 2 deletions lib/schemaType.js
Expand Up @@ -1542,7 +1542,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) {
}

if (value.$__ != null) {
value.$__.wasPopulated = value.$__.wasPopulated || true;
value.$__.wasPopulated = value.$__.wasPopulated || { value: value._id };
return value;
}

Expand All @@ -1568,7 +1568,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) {
!doc.$__.populated[path].options.options ||
!doc.$__.populated[path].options.options.lean) {
ret = new pop.options[populateModelSymbol](value);
ret.$__.wasPopulated = true;
ret.$__.wasPopulated = { value: ret._id };
}

return ret;
Expand Down
29 changes: 29 additions & 0 deletions lib/virtualType.js
@@ -1,7 +1,10 @@
'use strict';

const modelNamesFromRefPath = require('./helpers/populate/modelNamesFromRefPath');
const utils = require('./utils');

const modelSymbol = require('./helpers/symbols').modelSymbol;

/**
* VirtualType constructor
*
Expand Down Expand Up @@ -168,6 +171,32 @@ VirtualType.prototype.applySetters = function(value, doc) {
return v;
};

/**
* Get the names of models used to populate this model given a doc
*
* @param {Document} doc
* @return {Array<string> | null}
* @api private
*/

VirtualType.prototype._getModelNamesForPopulate = function _getModelNamesForPopulate(doc) {
if (this.options.refPath) {
return modelNamesFromRefPath(this.options.refPath, doc, this.path);
}

let normalizedRef = null;
if (typeof this.options.ref === 'function' && !this.options.ref[modelSymbol]) {
normalizedRef = this.options.ref.call(doc, doc);
} else {
normalizedRef = this.options.ref;
}
if (normalizedRef != null && !Array.isArray(normalizedRef)) {
return [normalizedRef];
}

return normalizedRef;
};

/*!
* exports
*/
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "mongoose",
"description": "Mongoose MongoDB ODM",
"version": "8.3.1",
"version": "8.3.2",
"author": "Guillermo Rauch <guillermo@learnboost.com>",
"keywords": [
"mongodb",
Expand Down

0 comments on commit 6ca274d

Please sign in to comment.