Skip to content

Commit de52899

Browse files
committed
Merge branch '7.x'
2 parents 31e0a69 + 834c29f commit de52899

File tree

7 files changed

+117
-12
lines changed

7 files changed

+117
-12
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
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+
18
8.2.0 / 2024-02-22
29
==================
310
* feat(model): add recompileSchema() function to models to allow applying schema changes after compiling #14306 #14296

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/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/helpers/discriminator/applyEmbeddedDiscriminators.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) {
1919
if (schemaType._appliedDiscriminators) {
2020
continue;
2121
}
22-
for (const disc of schemaType.schema._applyDiscriminators.keys()) {
23-
schemaType.discriminator(
24-
disc,
25-
schemaType.schema._applyDiscriminators.get(disc)
26-
);
22+
for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) {
23+
const discriminatorSchema = schemaType.schema._applyDiscriminators.get(discriminatorKey);
24+
applyEmbeddedDiscriminators(discriminatorSchema, seen);
25+
schemaType.discriminator(discriminatorKey, discriminatorSchema);
2726
}
2827
schemaType._appliedDiscriminators = true;
2928
}

lib/model.js

+4
Original file line numberDiff line numberDiff line change
@@ -2532,6 +2532,7 @@ Model.$where = function $where() {
25322532
* @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
25332533
* @param {Boolean} [options.includeResultMetadata] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
25342534
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
2535+
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
25352536
* @return {Query}
25362537
* @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
25372538
* @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
@@ -2624,6 +2625,7 @@ Model.findOneAndUpdate = function(conditions, update, options) {
26242625
* @param {Boolean} [options.new=false] if true, return the modified document rather than the original
26252626
* @param {Object|String} [options.select] sets the document fields to return.
26262627
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
2628+
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
26272629
* @return {Query}
26282630
* @see Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate()
26292631
* @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
@@ -3944,6 +3946,7 @@ Model.hydrate = function(obj, projection, options) {
39443946
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
39453947
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
39463948
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3949+
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
39473950
* @return {Query}
39483951
* @see Query docs https://mongoosejs.com/docs/queries.html
39493952
* @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
@@ -3983,6 +3986,7 @@ Model.updateMany = function updateMany(conditions, doc, options) {
39833986
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
39843987
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
39853988
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3989+
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
39863990
* @return {Query}
39873991
* @see Query docs https://mongoosejs.com/docs/queries.html
39883992
* @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output

lib/query.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -3191,6 +3191,7 @@ function prepareDiscriminatorCriteria(query) {
31913191
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
31923192
* @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`.
31933193
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3194+
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
31943195
* @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
31953196
* @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/
31963197
* @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html
@@ -3887,6 +3888,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
38873888
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
38883889
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
38893890
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3891+
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
38903892
* @param {Function} [callback] params are (error, writeOpResult)
38913893
* @return {Query} this
38923894
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
@@ -3955,7 +3957,8 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
39553957
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
39563958
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
39573959
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
3958-
@param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3960+
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3961+
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
39593962
* @param {Function} [callback] params are (error, writeOpResult)
39603963
* @return {Query} this
39613964
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()

test/document.test.js

+48
Original file line numberDiff line numberDiff line change
@@ -12791,6 +12791,54 @@ describe('document', function() {
1279112791
await doc2.save();
1279212792
});
1279312793

12794+
it('handles embedded recursive discriminators on nested path defined using Schema.prototype.discriminator (gh-14245)', async function() {
12795+
const baseSchema = new Schema({
12796+
type: { type: Number, required: true }
12797+
}, { discriminatorKey: 'type' });
12798+
12799+
class Base {
12800+
whoAmI() { return 'I am Base'; }
12801+
}
12802+
12803+
baseSchema.loadClass(Base);
12804+
12805+
class NumberTyped extends Base {
12806+
whoAmI() { return 'I am NumberTyped'; }
12807+
}
12808+
12809+
class StringTyped extends Base {
12810+
whoAmI() { return 'I am StringTyped'; }
12811+
}
12812+
12813+
const selfRefSchema = new Schema({
12814+
self: { type: [baseSchema], required: true }
12815+
});
12816+
12817+
class SelfReferenceTyped extends Base {
12818+
whoAmI() { return 'I am SelfReferenceTyped'; }
12819+
}
12820+
12821+
selfRefSchema.loadClass(SelfReferenceTyped);
12822+
baseSchema.discriminator(5, selfRefSchema);
12823+
12824+
const numberTypedSchema = new Schema({}).loadClass(NumberTyped);
12825+
const stringTypedSchema = new Schema({}).loadClass(StringTyped);
12826+
baseSchema.discriminator(1, numberTypedSchema);
12827+
baseSchema.discriminator(3, stringTypedSchema);
12828+
const containerSchema = new Schema({ items: [baseSchema] });
12829+
const containerModel = db.model('Test', containerSchema);
12830+
12831+
const instance = await containerModel.create({
12832+
items: [{ type: 5, self: [{ type: 1 }, { type: 3 }] }]
12833+
});
12834+
12835+
assert.equal(instance.items[0].whoAmI(), 'I am SelfReferenceTyped');
12836+
assert.deepStrictEqual(instance.items[0].self.map(item => item.whoAmI()), [
12837+
'I am NumberTyped',
12838+
'I am StringTyped'
12839+
]);
12840+
});
12841+
1279412842
it('can use `collection` as schema name (gh-13956)', async function() {
1279512843
const schema = new mongoose.Schema({ name: String, collection: String });
1279612844
const Test = db.model('Test', schema);

0 commit comments

Comments
 (0)