Skip to content

Commit

Permalink
Merge branch '8.0' into IslandRhythms/gh-13369
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Jul 26, 2023
2 parents d9fd789 + 92876b5 commit df6ad8e
Show file tree
Hide file tree
Showing 74 changed files with 1,263 additions and 649 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
7.4.1 / 2023-07-24
==================
* fix(document): correctly clean up nested subdocs modified state on save() #13644 #13609
* fix(schema): avoid propagating toObject.transform and toJSON.transform option to implicitly created schemas #13634 #13599
* fix: prevent schema options overwriting user defined writeConcern #13612 #13592
* types: correctly handle pre('deleteOne', { document: true }) #13632
* types(schema): handle type: Schema.Types.Map in TypeScript #13628
* types: Add inline comment to to tell the default value of the runValidator flag in the queryOptions types #13636 [omran95](https://github.com/omran95)
* docs: rework several code examples that still use callbacks #13635 #13616
* docs: remove callbacks from validation description #13638 #13501

7.4.0 / 2023-07-18
==================
* perf: speed up mapOfSubdocs benchmark by 4x by avoiding unnecessary O(n^2) loop in getPathsToValidate() #13614
* feat: upgrade to MongoDB Node.js driver 5.7.0 #13591
* feat: support generating custom cast error message with a function #13608 #3162
* feat(query): support MongoDB driver's includeResultMetadata option for findOneAndUpdate #13584 #13539
* feat(connection): add Connection.prototype.removeDb() for removing a related connection #13580 #11821
* feat(query): delay converting documents into POJOs until query execution, allow querying subdocuments with defaults disabled #13522
* feat(model): add option "aggregateErrors" for create() #13544 [hasezoey](https://github.com/hasezoey)
* feat(schema): add collectionOptions option to schemas #13513
* fix: move all MongoDB-specific connection logic into driver layer, add createClient() method to handle creating MongoClient #13542
* fix(document): allow setting keys with dots in mixed paths underneath nested paths #13536
* types: augment bson.ObjectId instead of adding on own type #13515 #12537 [hasezoey](https://github.com/hasezoey)
* docs(guide): fix md lint #13593 [hasezoey](https://github.com/hasezoey)
* docs: changed the code from 'await author.save()' to 'await story1.save()' #13596 [SomSingh23](https://github.com/SomSingh23)

6.11.4 / 2023-07-17
===================
* perf: speed up mapOfSubdocs benchmark by 4x by avoiding unnecessary O(n^2) loop in getPathsToValidate() #13614
Expand Down
24 changes: 2 additions & 22 deletions docs/async-await.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,7 @@ This is especially helpful for avoiding callback hell when executing multiple as
Each of the three functions below retrieves a record from the database, updates it, and prints the updated record to the console.

```javascript
// Works.
function callbackUpdate() {
MyModel.findOne({ firstName: 'franklin', lastName: 'roosevelt' }, function(err, doc) {
if (err) {
handleError(err);
}

doc.middleName = 'delano';

doc.save(function(err, updatedDoc) {
if (err) {
handleError(err);
}

// Final logic is 2 callbacks deep
console.log(updatedDoc);
});
});
}

// Better.
// Using promise chaining
function thenUpdate() {
MyModel.findOne({ firstName: 'franklin', lastName: 'roosevelt' })
.then(function(doc) {
Expand All @@ -44,7 +24,7 @@ function thenUpdate() {
});
}

// Best?
// Using async/await
async function awaitUpdate() {
try {
const doc = await MyModel.findOne({
Expand Down
110 changes: 16 additions & 94 deletions docs/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,108 +9,30 @@ cause any problems for your application. Please [report any issues on GitHub](ht

To fix all deprecation warnings, follow the below steps:

* Replace `update()` with `updateOne()`, `updateMany()`, or `replaceOne()`
* Replace `remove()` with `deleteOne()` or `deleteMany()`.
* Replace `count()` with `countDocuments()`, unless you want to count how many documents are in the whole collection (no filter). In the latter case, use `estimatedDocumentCount()`.
* Replace `rawResult: true` with `includeResultMetadata: false` in `findOneAndUpdate()`, `findOneAndReplace()`, `findOneAndDelete()` calls.

Read below for more a more detailed description of each deprecation warning.

<h2 id="remove"><a href="#remove"><code>remove()</code></a></h2>
<h2 id="rawresult"><a href="#rawresult"><code>rawResult</code></a></h2>

The MongoDB driver's [`remove()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#remove) is deprecated in favor of `deleteOne()` and `deleteMany()`. This is to comply with
the [MongoDB CRUD specification](https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst),
which aims to provide a consistent API for CRUD operations across all MongoDB
drivers.

```txt
DeprecationWarning: collection.remove is deprecated. Use deleteOne,
deleteMany, or bulkWrite instead.
```

To remove this deprecation warning, replace any usage of `remove()` with
`deleteMany()`, *unless* you specify the [`single` option to `remove()`](api/model.html#model_Model-remove). The `single`
option limited `remove()` to deleting at most one document, so you should
replace `remove(filter, { single: true })` with `deleteOne(filter)`.
As of Mongoose 7.4.0, the `rawResult` option to `findOneAndUpdate()` is deprecated.
You should instead use the `includeResultMetadata` option, which the MongoDB Node.js driver's new option that replaces `rawResult`.

```javascript
// Replace this:
MyModel.remove({ foo: 'bar' });
// With this:
MyModel.deleteMany({ foo: 'bar' });

// Replace this:
MyModel.remove({ answer: 42 }, { single: true });
// With this:
MyModel.deleteOne({ answer: 42 });
```
const doc = await Test.findOneAndUpdate(
{ name: 'Test' },
{ name: 'Test Testerson' },
{ rawResult: true }
);

<h2 id="update"><a href="#update"><code>update()</code></a></h2>

Like `remove()`, the [`update()` function](api/model.html#model_Model-update) is deprecated in favor
of the more explicit [`updateOne()`](api/model.html#model_Model-updateOne), [`updateMany()`](api/model.html#model_Model-updateMany), and [`replaceOne()`](api/model.html#model_Model-replaceOne) functions. You should replace
`update()` with `updateOne()`, unless you use the [`multi` or `overwrite` options](api/model.html#model_Model-update).

```txt
collection.update is deprecated. Use updateOne, updateMany, or bulkWrite
instead.
```

```javascript
// Replace this:
MyModel.update({ foo: 'bar' }, { answer: 42 });
// With this:
MyModel.updateOne({ foo: 'bar' }, { answer: 42 });

// If you use `overwrite: true`, you should use `replaceOne()` instead:
MyModel.update(filter, update, { overwrite: true });
// Replace with this:
MyModel.replaceOne(filter, update);

// If you use `multi: true`, you should use `updateMany()` instead:
MyModel.update(filter, update, { multi: true });
// Replace with this:
MyModel.updateMany(filter, update);
const doc = await Test.findOneAndUpdate(
{ name: 'Test' },
{ name: 'Test Testerson' },
{ includeResultMetadata: false }
);
```

<h2 id="count"><a href="#count"><code>count()</code></a></h2>

The MongoDB server has deprecated the `count()` function in favor of two
separate functions, [`countDocuments()`](api/query.html#Query.prototype.countDocuments()) and
[`estimatedDocumentCount()`](api/query.html#Query.prototype.estimatedDocumentCount()).

```txt
DeprecationWarning: collection.count is deprecated, and will be removed in a future version. Use collection.countDocuments or collection.estimatedDocumentCount instead
```

The difference between the two is `countDocuments()` can accept a filter
parameter like [`find()`](api/query.html#Query.prototype.find()). The `estimatedDocumentCount()`
function is faster, but can only tell you the total number of documents in
a collection. You cannot pass a `filter` to `estimatedDocumentCount()`.

To migrate, replace `count()` with `countDocuments()` *unless* you do not
pass any arguments to `count()`. If you use `count()` to count all documents
in a collection as opposed to counting documents that match a query, use
`estimatedDocumentCount()` instead of `countDocuments()`.

```javascript
// Replace this:
MyModel.count({ answer: 42 });
// With this:
MyModel.countDocuments({ answer: 42 });

// If you're counting all documents in the collection, use
// `estimatedDocumentCount()` instead.
MyModel.count();
// Replace with:
MyModel.estimatedDocumentCount();

// Replace this:
MyModel.find({ answer: 42 }).count().exec();
// With this:
MyModel.find({ answer: 42 }).countDocuments().exec();

// Replace this:
MyModel.find().count().exec();
// With this, since there's no filter
MyModel.find().estimatedDocumentCount().exec();
```
The `rawResult` option only affects Mongoose; the MongoDB Node.js driver still returns the full result metadata, Mongoose just parses out the raw document.
The `includeResultMetadata` option also tells the MongoDB Node.js driver to only return the document, not the full `ModifyResult` object.
24 changes: 7 additions & 17 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ const schema = new mongoose.Schema({
});
const Model = db.model('Test', schema);

Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
console.log(err); // No error, unless index was already built
});
// No error, unless index was already built
await Model.create([{ name: 'Val' }, { name: 'Val' }]);
```

However, if you wait for the index to build using the `Model.on('index')` event, attempts to save duplicates will correctly error.
Expand All @@ -92,21 +91,12 @@ const schema = new mongoose.Schema({
});
const Model = db.model('Test', schema);

Model.on('index', function(err) { // <-- Wait for model's indexes to finish
assert.ifError(err);
Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
console.log(err);
});
});

// Promise based alternative. `init()` returns a promise that resolves
// when the indexes have finished building successfully. The `init()`
// Wait for model's indexes to finish. The `init()`
// function is idempotent, so don't worry about triggering an index rebuild.
Model.init().then(function() {
Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
console.log(err);
});
});
await Model.init();

// Throws a duplicate key error
await Model.create([{ name: 'Val' }, { name: 'Val' }]);
```

MongoDB persists indexes, so you only need to rebuild indexes if you're starting
Expand Down
25 changes: 25 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ Valid options:
* [skipVersioning](#skipVersioning)
* [timestamps](#timestamps)
* [storeSubdocValidationError](#storeSubdocValidationError)
* [collectionOptions](#collectionOptions)
* [methods](#methods)
* [query](#query-helpers)

Expand Down Expand Up @@ -1399,6 +1400,30 @@ const Parent = mongoose.model('Parent', parentSchema);
new Parent({ child: {} }).validateSync().errors;
```

<h2 id="collectionOptions">
<a href="#collectionOptions">
option: collectionOptions
</a>
</h2>

Options like [`collation`](#collation) and [`capped`](#capped) affect the options Mongoose passes to MongoDB when creating a new collection.
Mongoose schemas support most [MongoDB `createCollection()` options](https://www.mongodb.com/docs/manual/reference/method/db.createCollection/), but not all.
You can use the `collectionOptions` option to set any `createCollection()` options; Mongoose will use `collectionOptions` as the default values when calling `createCollection()` for your schema.

```javascript
const schema = new Schema({ name: String }, {
autoCreate: false,
collectionOptions: {
capped: true,
max: 1000
}
});
const Test = mongoose.model('Test', schema);

// Equivalent to `createCollection({ capped: true, max: 1000 })`
await Test.createCollection();
```

<h2 id="es6-classes"><a href="#es6-classes">With ES6 Classes</a></h2>

Schemas have a [`loadClass()` method](api/schema.html#schema_Schema-loadClass)
Expand Down
9 changes: 4 additions & 5 deletions docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,11 +532,10 @@ schema.post('update', function(error, res, next) {
});

const people = [{ name: 'Axl Rose' }, { name: 'Slash' }];
Person.create(people, function(error) {
Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) {
// `error.message` will be "There was a duplicate key error"
});
});
await Person.create(people);

// Throws "There was a duplicate key error"
await Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } });
```

Error handling middleware can transform an error, but it can't remove the
Expand Down
42 changes: 7 additions & 35 deletions docs/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,44 +104,16 @@ A full list of [Query helper functions can be found in the API docs](api/query.h
</a>
</h2>

Mongoose queries are **not** promises. They have a `.then()`
function for [co](https://www.npmjs.com/package/co) and
[async/await](http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html)
as a convenience. However, unlike promises, calling a query's `.then()`
can execute the query multiple times.

For example, the below code will execute 3 `updateMany()` calls, one
because of the callback, and two because `.then()` is called twice.
Mongoose queries are **not** promises.
Queries are [thenables](https://masteringjs.io/tutorials/fundamentals/thenable), meaning they have a `.then()` method for [async/await](http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html) as a convenience.
However, unlike promises, calling a query's `.then()` executes the query, so calling `then()` multiple times will throw an error.

```javascript
const q = MyModel.updateMany({}, { isDeleted: true }, function() {
console.log('Update 1');
});

q.then(() => console.log('Update 2'));
q.then(() => console.log('Update 3'));
```
const q = MyModel.updateMany({}, { isDeleted: true });

Don't mix using callbacks and promises with queries, or you may end up
with duplicate operations. That's because passing a callback to a query function
immediately executes the query, and calling [`then()`](https://masteringjs.io/tutorials/fundamentals/then)
executes the query again.

Mixing promises and callbacks can lead to duplicate entries in arrays.
For example, the below code inserts 2 entries into the `tags` array, **not** just 1.

```javascript
const BlogPost = mongoose.model('BlogPost', new Schema({
title: String,
tags: [String]
}));

// Because there's both `await` **and** a callback, this `updateOne()` executes twice
// and thus pushes the same string into `tags` twice.
const update = { $push: { tags: ['javascript'] } };
await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => {
console.log(res);
});
await q.then(() => console.log('Update 2'));
// Throws "Query was already executed: Test.updateMany({}, { isDeleted: true })"
await q.then(() => console.log('Update 3'));
```

<h2 id="refs"><a href="#refs">References to other documents</a></h2>
Expand Down
30 changes: 23 additions & 7 deletions docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,35 @@ thrown.

## Cast Errors

Before running validators, Mongoose attempts to coerce values to the
correct type. This process is called *casting* the document. If
casting fails for a given path, the `error.errors` object will contain
a `CastError` object.
Before running validators, Mongoose attempts to coerce values to the correct type. This process is called *casting* the document.
If casting fails for a given path, the `error.errors` object will contain a `CastError` object.

Casting runs before validation, and validation does not run if casting
fails. That means your custom validators may assume `v` is `null`,
`undefined`, or an instance of the type specified in your schema.
Casting runs before validation, and validation does not run if casting fails.
That means your custom validators may assume `v` is `null`, `undefined`, or an instance of the type specified in your schema.

```acquit
[require:Cast Errors]
```

By default, Mongoose cast error messages look like `Cast to Number failed for value "pie" at path "numWheels"`.
You can overwrite Mongoose's default cast error message by the `cast` option on your SchemaType to a string as follows.

```acquit
[require:Cast Error Message Overwrite]
```

Mongoose's cast error message templating supports the following parameters:

* `{PATH}`: the path that failed to cast
* `{VALUE}`: a string representation of the value that failed to cast
* `{KIND}`: the type that Mongoose attempted to cast to, like `'String'` or `'Number'`

You can also define a function that Mongoose will call to get the cast error message as follows.

```acquit
[require:Cast Error Message Function Overwrite]
```

## Global SchemaType Validation

In addition to defining custom validators on individual schema paths, you can also configure a custom validator to run on every instance of a given `SchemaType`.
Expand Down

0 comments on commit df6ad8e

Please sign in to comment.