Skip to content

Commit f6ea526

Browse files
committed
Merge branch 'master' into vkarpov15/gh-14289
2 parents 0d8189a + ec6a999 commit f6ea526

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1225
-296
lines changed

.github/workflows/test.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767

6868
- name: Load MongoDB binary cache
6969
id: cache-mongodb-binaries
70-
uses: actions/cache@v3
70+
uses: actions/cache@v4
7171
with:
7272
path: ~/.cache/mongodb-binaries
7373
key: ${{ matrix.os }}-${{ matrix.mongodb }}
@@ -101,7 +101,7 @@ jobs:
101101
node-version: 16
102102
- name: Load MongoDB binary cache
103103
id: cache-mongodb-binaries
104-
uses: actions/cache@v3
104+
uses: actions/cache@v4
105105
with:
106106
path: ~/.cache/mongodb-binaries
107107
key: deno-${{ env.MONGOMS_VERSION }}
@@ -141,4 +141,4 @@ jobs:
141141
- name: Check out repo
142142
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
143143
- name: Dependency review
144-
uses: actions/dependency-review-action@v3
144+
uses: actions/dependency-review-action@v4

CHANGELOG.md

+33
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
7.6.9 / 2024-02-26
2+
==================
3+
* fix(document): handle embedded recursive discriminators on nested path defined using Schema.prototype.discriminator #14256 #14245
4+
* types(model): correct return type for findByIdAndDelete() #14233 #14190
5+
* docs(connections): add note about using asPromise() with createConnection() for error handling #14364 #14266
6+
* docs(model+query+findoneandupdate): add more details about overwriteDiscriminatorKey option to docs #14264 #14246
7+
8+
8.2.0 / 2024-02-22
9+
==================
10+
* feat(model): add recompileSchema() function to models to allow applying schema changes after compiling #14306 #14296
11+
* feat: add middleware for bulkWrite() and createCollection() #14358 #14263 #7893
12+
* feat(model): add `hydratedPopulatedDocs` option to make hydrate recursively hydrate populated docs #14352 #4727
13+
* feat(connection): add withSession helper #14339 #14330
14+
15+
8.1.3 / 2024-02-16
16+
==================
17+
* fix: avoid corrupting $set-ed arrays when transaction error occurs #14346 #14340
18+
* fix(populate): handle ref() functions that return a model instance #14343 #14249
19+
* fix: insert version key when using insertMany even if `toObject.versionKey` set to false #14344
20+
* fix(cursor): make aggregation cursor support transform option to match query cursor #14348 #14331
21+
* docs(document): clarify that transform function option applies to subdocs #13757
22+
23+
8.1.2 / 2024-02-08
24+
==================
25+
* fix: include virtuals in document array toString() output if toObject.virtuals set #14335 #14315
26+
* fix(document): handle setting nested path to spread doc with extra properties #14287 #14269
27+
* fix(populate): call setter on virtual populated path with populated doc instead of undefined #14314
28+
* fix(QueryCursor): remove callback parameter of AggregationCursor and QueryCursor #14299 [DevooKim](https://github.com/DevooKim)
29+
* types: add typescript support for arbitrary fields for the options parameter of Model functions which are of type MongooseQueryOptions #14342 #14341 [FaizBShah](https://github.com/FaizBShah)
30+
* types(model): correct return type for findOneAndUpdate with includeResultMetadata and lean set #14336 #14303
31+
* types(connection): add type definition for `createCollections()` #14295 #14279
32+
* docs(timestamps): clarify that replaceOne() and findOneAndReplace() overwrite timestamps #14337 #14309
33+
134
8.1.1 / 2024-01-24
235
==================
336
* fix(model): throw readable error when calling Model() with a string instead of model() #14288 #14281

docs/connections.md

+13-5
Original file line numberDiff line numberDiff line change
@@ -426,16 +426,24 @@ The `mongoose.createConnection()` function takes the same arguments as
426426
const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);
427427
```
428428

429-
This [connection](api/connection.html#connection_Connection) object is then used to
430-
create and retrieve [models](api/model.html#model_Model). Models are
431-
**always** scoped to a single connection.
429+
This [connection](api/connection.html#connection_Connection) object is then used to create and retrieve [models](api/model.html#model_Model).
430+
Models are **always** scoped to a single connection.
432431

433432
```javascript
434433
const UserModel = conn.model('User', userSchema);
435434
```
436435

437-
If you use multiple connections, you should make sure you export schemas,
438-
**not** models. Exporting a model from a file is called the *export model pattern*.
436+
The `createConnection()` function returns a connection instance, not a promise.
437+
If you want to use `await` to make sure Mongoose successfully connects to MongoDB, use the [`asPromise()` function](api/connection.html#Connection.prototype.asPromise()):
438+
439+
```javascript
440+
// `asPromise()` returns a promise that resolves to the connection
441+
// once the connection succeeds, or rejects if connection failed.
442+
const conn = await mongoose.createConnection(connectionString).asPromise();
443+
```
444+
445+
If you use multiple connections, you should make sure you export schemas, **not** models.
446+
Exporting a model from a file is called the *export model pattern*.
439447
The export model pattern is limited because you can only use one connection.
440448

441449
```javascript

docs/middleware.md

+4
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,17 @@ Model middleware is supported for the following model functions.
6767
Don't confuse model middleware and document middleware: model middleware hooks into *static* functions on a `Model` class, document middleware hooks into *methods* on a `Model` class.
6868
In model middleware functions, `this` refers to the model.
6969

70+
* [bulkWrite](api/model.html#model_Model-bulkWrite)
71+
* [createCollection](api/model.html#model_Model-createCollection)
7072
* [insertMany](api/model.html#model_Model-insertMany)
7173

7274
Here are the possible strings that can be passed to `pre()`
7375

7476
* aggregate
77+
* bulkWrite
7578
* count
7679
* countDocuments
80+
* createCollection
7781
* deleteOne
7882
* deleteMany
7983
* estimatedDocumentCount

docs/timestamps.md

+30
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ console.log(doc.updatedAt); // 2022-02-26T17:08:13.991Z
4747

4848
// Mongoose also blocks changing `createdAt` and sets its own `updatedAt`
4949
// on `findOneAndUpdate()`, `updateMany()`, and other query operations
50+
// **except** `replaceOne()` and `findOneAndReplace()`.
5051
doc = await User.findOneAndUpdate(
5152
{ _id: doc._id },
5253
{ name: 'test3', createdAt: new Date(0), updatedAt: new Date(0) },
@@ -56,6 +57,35 @@ console.log(doc.createdAt); // 2022-02-26T17:08:13.930Z
5657
console.log(doc.updatedAt); // 2022-02-26T17:08:14.008Z
5758
```
5859

60+
Keep in mind that `replaceOne()` and `findOneAndReplace()` overwrite all non-`_id` properties, **including** immutable properties like `createdAt`.
61+
Calling `replaceOne()` or `findOneAndReplace()` will update the `createdAt` timestamp as shown below.
62+
63+
```javascript
64+
// `findOneAndReplace()` and `replaceOne()` without timestamps specified in `replacement`
65+
// sets `createdAt` and `updatedAt` to current time.
66+
doc = await User.findOneAndReplace(
67+
{ _id: doc._id },
68+
{ name: 'test3' },
69+
{ new: true }
70+
);
71+
console.log(doc.createdAt); // 2022-02-26T17:08:14.008Z
72+
console.log(doc.updatedAt); // 2022-02-26T17:08:14.008Z
73+
74+
// `findOneAndReplace()` and `replaceOne()` with timestamps specified in `replacement`
75+
// sets `createdAt` and `updatedAt` to the values in `replacement`.
76+
doc = await User.findOneAndReplace(
77+
{ _id: doc._id },
78+
{
79+
name: 'test3',
80+
createdAt: new Date('2022-06-01'),
81+
updatedAt: new Date('2022-06-01')
82+
},
83+
{ new: true }
84+
);
85+
console.log(doc.createdAt); // 2022-06-01T00:00:00.000Z
86+
console.log(doc.updatedAt); // 2022-06-01T00:00:00.000Z
87+
```
88+
5989
## Alternate Property Names
6090

6191
For the purposes of these docs, we'll always refer to `createdAt` and `updatedAt`.

docs/tutorials/findoneandupdate.md

+37-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ However, there are some cases where you need to use [`findOneAndUpdate()`](https
77
* [Atomic Updates](#atomic-updates)
88
* [Upsert](#upsert)
99
* [The `includeResultMetadata` Option](#includeresultmetadata)
10+
* [Updating Discriminator Keys](#updating-discriminator-keys)
1011

1112
## Getting Started
1213

13-
As the name implies, `findOneAndUpdate()` finds the first document that matches a given `filter`, applies an `update`, and returns the document. By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied.
14+
As the name implies, `findOneAndUpdate()` finds the first document that matches a given `filter`, applies an `update`, and returns the document.
15+
The `findOneAndUpdate()` function has the following signature:
16+
17+
```javascript
18+
function findOneAndUpdate(filter, update, options) {}
19+
```
20+
21+
By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied.
1422

1523
```acquit
1624
[require:Tutorial.*findOneAndUpdate.*basic case]
@@ -78,3 +86,31 @@ Here's what the `res` object from the above example looks like:
7886
age: 29 },
7987
ok: 1 }
8088
```
89+
90+
## Updating Discriminator Keys
91+
92+
Mongoose prevents updating the [discriminator key](../discriminators.html#discriminator-keys) using `findOneAndUpdate()` by default.
93+
For example, suppose you have the following discriminator models.
94+
95+
```javascript
96+
const eventSchema = new mongoose.Schema({ time: Date });
97+
const Event = db.model('Event', eventSchema);
98+
99+
const ClickedLinkEvent = Event.discriminator(
100+
'ClickedLink',
101+
new mongoose.Schema({ url: String })
102+
);
103+
104+
const SignedUpEvent = Event.discriminator(
105+
'SignedUp',
106+
new mongoose.Schema({ username: String })
107+
);
108+
```
109+
110+
Mongoose will remove `__t` (the default discriminator key) from the `update` parameter, if `__t` is set.
111+
This is to prevent unintentional updates to the discriminator key; for example, if you're passing untrusted user input to the `update` parameter.
112+
However, you can tell Mongoose to allow updating the discriminator key by setting the `overwriteDiscriminatorKey` option to `true` as shown below.
113+
114+
```acquit
115+
[require:use overwriteDiscriminatorKey to change discriminator key]
116+
```

lib/aggregate.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1019,8 +1019,8 @@ Aggregate.prototype.exec = async function exec() {
10191019
const model = this._model;
10201020
const collection = this._model.collection;
10211021

1022-
applyGlobalMaxTimeMS(this.options, model);
1023-
applyGlobalDiskUse(this.options, model);
1022+
applyGlobalMaxTimeMS(this.options, model.db.options, model.base.options);
1023+
applyGlobalDiskUse(this.options, model.db.options, model.base.options);
10241024

10251025
if (this.options && this.options.cursor) {
10261026
return new AggregationCursor(this);

lib/connection.js

+46
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,28 @@ Connection.prototype.createCollections = async function createCollections(option
443443
return result;
444444
};
445445

446+
/**
447+
* A convenience wrapper for `connection.client.withSession()`.
448+
*
449+
* #### Example:
450+
*
451+
* await conn.withSession(async session => {
452+
* const doc = await TestModel.findOne().session(session);
453+
* });
454+
*
455+
* @method withSession
456+
* @param {Function} executor called with 1 argument: a `ClientSession` instance
457+
* @return {Promise} resolves to the return value of the executor function
458+
* @api public
459+
*/
460+
461+
Connection.prototype.withSession = async function withSession(executor) {
462+
if (arguments.length === 0) {
463+
throw new Error('Please provide an executor function');
464+
}
465+
return await this.client.withSession(executor);
466+
};
467+
446468
/**
447469
* _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
448470
* for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
@@ -807,6 +829,30 @@ Connection.prototype.openUri = async function openUri(uri, options) {
807829
return this;
808830
};
809831

832+
/*!
833+
* Treat `on('error')` handlers as handling the initialConnection promise
834+
* to avoid uncaught exceptions when using `on('error')`. See gh-14377.
835+
*/
836+
837+
Connection.prototype.on = function on(event, callback) {
838+
if (event === 'error' && this.$initialConnection) {
839+
this.$initialConnection.catch(() => {});
840+
}
841+
return EventEmitter.prototype.on.call(this, event, callback);
842+
};
843+
844+
/*!
845+
* Treat `once('error')` handlers as handling the initialConnection promise
846+
* to avoid uncaught exceptions when using `on('error')`. See gh-14377.
847+
*/
848+
849+
Connection.prototype.once = function on(event, callback) {
850+
if (event === 'error' && this.$initialConnection) {
851+
this.$initialConnection.catch(() => {});
852+
}
853+
return EventEmitter.prototype.once.call(this, event, callback);
854+
};
855+
810856
/*!
811857
* ignore
812858
*/

lib/constants.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
/*!
4+
* ignore
5+
*/
6+
7+
const queryOperations = Object.freeze([
8+
// Read
9+
'countDocuments',
10+
'distinct',
11+
'estimatedDocumentCount',
12+
'find',
13+
'findOne',
14+
// Update
15+
'findOneAndReplace',
16+
'findOneAndUpdate',
17+
'replaceOne',
18+
'updateMany',
19+
'updateOne',
20+
// Delete
21+
'deleteMany',
22+
'deleteOne',
23+
'findOneAndDelete'
24+
]);
25+
26+
exports.queryOperations = queryOperations;
27+
28+
/*!
29+
* ignore
30+
*/
31+
32+
const queryMiddlewareFunctions = queryOperations.concat([
33+
'validate'
34+
]);
35+
36+
exports.queryMiddlewareFunctions = queryMiddlewareFunctions;

lib/cursor/aggregationCursor.js

+8
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,20 @@ util.inherits(AggregationCursor, Readable);
5757
function _init(model, c, agg) {
5858
if (!model.collection.buffer) {
5959
model.hooks.execPre('aggregate', agg, function() {
60+
if (typeof agg.options?.cursor?.transform === 'function') {
61+
c._transforms.push(agg.options.cursor.transform);
62+
}
63+
6064
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
6165
c.emit('cursor', c.cursor);
6266
});
6367
} else {
6468
model.collection.emitter.once('queue', function() {
6569
model.hooks.execPre('aggregate', agg, function() {
70+
if (typeof agg.options?.cursor?.transform === 'function') {
71+
c._transforms.push(agg.options.cursor.transform);
72+
}
73+
6674
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
6775
c.emit('cursor', c.cursor);
6876
});

0 commit comments

Comments
 (0)