diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27d572732bb..74093f5ef3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+6.4.5 / 2022-07-18
+==================
+ * fix(model+timestamps): set timestamps on subdocuments in insertMany() #12060
+ * fix: correct isAtlas check #12110 [skrtheboss](https://github.com/skrtheboss)
+ * fix(types): fix various issues with auto typed schemas #12042 [mohammad0-0ahmad](https://github.com/mohammad0-0ahmad)
+ * fix(types): allow any value for AddFields #12096
+ * fix(types): allow arbitrary expressions for ConcatArrays #12058
+ * fix(types): make $addToSet fields mutable to allow programatically constructing $addToSet #12091
+ * fix(types): add $let as a possible expression to $addFields #12087 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez)
+ * fix(types): fix $switch expression type #12088 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez)
+ * fix(types): correct options type for syncIndexes() #12101 [lpizzinidev](https://github.com/lpizzinidev)
+ * fix(types): avoid treating | undefined types as any in `Require_id` to better support `_id: String` with auto-typed schemas #12070
+ * docs: fix up various jsdoc issues #12086 [hasezoey](https://github.com/hasezoey)
+ * docs: add sanitizeFilter to mongoose.set() options #12112 [pathei-kosmos](https://github.com/pathei-kosmos)
+
6.4.4 / 2022-07-08
==================
* fix(types): allow using an object to configure timestamps #12061 [lantw44](https://github.com/lantw44)
diff --git a/docs/migrating_to_6.md b/docs/migrating_to_6.md
index 270624e3368..68e4a8249e0 100644
--- a/docs/migrating_to_6.md
+++ b/docs/migrating_to_6.md
@@ -194,8 +194,8 @@ mongoose.isValidObjectId(new User({ name: 'test' })); // true
// character hex strings.
mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true
mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true
-mongoose.isValidObjectId('0123456789ab'); // false
-mongoose.isValidObjectId(6); // false
+mongoose.isObjectIdOrHexString('0123456789ab'); // false
+mongoose.isObjectIdOrHexString(6); // false
```
diff --git a/lib/document.js b/lib/document.js
index 72c12c75442..e6f73aeb774 100644
--- a/lib/document.js
+++ b/lib/document.js
@@ -1149,8 +1149,8 @@ Document.prototype.$set = function $set(path, val, type, options) {
}
if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') {
- $applyDefaultsToNested(path[key], prefix + key, this);
this.$set(prefix + key, path[key], constructing, Object.assign({}, options, { _skipMarkModified: true }));
+ $applyDefaultsToNested(this.$get(prefix + key), prefix + key, this);
continue;
} else if (strict) {
// Don't overwrite defaults with undefined keys (gh-3981) (gh-9039)
diff --git a/lib/query.js b/lib/query.js
index 1bfed85a27e..634616fe79a 100644
--- a/lib/query.js
+++ b/lib/query.js
@@ -4019,7 +4019,9 @@ Query.prototype._findAndModify = function(type, callback) {
*/
function _completeOneLean(schema, doc, path, res, opts, callback) {
- if (opts.lean && opts.lean.transform) {
+ if (opts.lean && typeof opts.lean.transform === 'function') {
+ opts.lean.transform(doc);
+
for (let i = 0; i < schema.childSchemas.length; i++) {
const childPath = path ? path + '.' + schema.childSchemas[i].model.path : schema.childSchemas[i].model.path;
const _schema = schema.childSchemas[i].schema;
@@ -4053,7 +4055,11 @@ function _completeOneLean(schema, doc, path, res, opts, callback) {
*/
function _completeManyLean(schema, docs, path, opts, callback) {
- if (opts.lean && opts.lean.transform) {
+ if (opts.lean && typeof opts.lean.transform === 'function') {
+ for (const doc of docs) {
+ opts.lean.transform(doc);
+ }
+
for (let i = 0; i < schema.childSchemas.length; i++) {
const childPath = path ? path + '.' + schema.childSchemas[i].model.path : schema.childSchemas[i].model.path;
const _schema = schema.childSchemas[i].schema;
diff --git a/lib/schema.js b/lib/schema.js
index 781ccdbbcf8..730bddeac49 100644
--- a/lib/schema.js
+++ b/lib/schema.js
@@ -554,6 +554,10 @@ Schema.prototype.add = function add(obj, prefix) {
const keys = Object.keys(obj);
const typeKey = this.options.typeKey;
for (const key of keys) {
+ if (utils.specialProperties.has(key)) {
+ continue;
+ }
+
const fullPath = prefix + key;
const val = obj[key];
@@ -854,6 +858,9 @@ Schema.prototype.path = function(path, obj) {
let fullPath = '';
for (const sub of subpaths) {
+ if (utils.specialProperties.has(sub)) {
+ throw new Error('Cannot set special property `' + sub + '` on a schema');
+ }
fullPath = fullPath += (fullPath.length > 0 ? '.' : '') + sub;
if (!branch[sub]) {
this.nested[fullPath] = true;
diff --git a/package.json b/package.json
index 35c4100ee66..bfb28d8cf16 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mongoose",
"description": "Mongoose MongoDB ODM",
- "version": "6.4.4",
+ "version": "6.4.5",
"author": "Guillermo Rauch ",
"keywords": [
"mongodb",
@@ -21,7 +21,7 @@
"dependencies": {
"bson": "^4.6.2",
"kareem": "2.4.1",
- "mongodb": "4.8.0",
+ "mongodb": "4.7.0",
"mpath": "0.9.0",
"mquery": "4.0.3",
"ms": "2.1.3",
diff --git a/test/document.test.js b/test/document.test.js
index 160b14fbe3d..359a0ee050d 100644
--- a/test/document.test.js
+++ b/test/document.test.js
@@ -8831,7 +8831,7 @@ describe('document', function() {
assert.ok(!user.updatedAt);
});
- it('Sets default when passing undefined as value for a key in a nested subdoc (gh-9039)', async function() {
+ it('Sets default when passing undefined as value for a key in a nested subdoc (gh-12102) (gh-9039)', async function() {
const Test = db.model('Test', {
nested: {
prop: {
@@ -8841,9 +8841,11 @@ describe('document', function() {
}
});
-
- const doc = await Test.create({ nested: { prop: undefined } });
+ const obj = { nested: { prop: undefined } };
+ const doc = await Test.create(obj);
assert.equal(doc.nested.prop, 'some default value');
+
+ assert.deepStrictEqual(obj, { nested: { prop: undefined } });
});
it('allows accessing $locals when initializing (gh-9098)', function() {
diff --git a/test/query.test.js b/test/query.test.js
index 746774c5c42..1ebc5320aa4 100644
--- a/test/query.test.js
+++ b/test/query.test.js
@@ -4006,22 +4006,28 @@ describe('Query', function() {
});
const Test = db.model('gh10423', testSchema);
await Test.create({ name: 'foo', foo: [{ sub: 'Test' }, { sub: 'Testerson' }], otherName: { nickName: 'Bar' } });
- const result = await Test.find().lean({ transform: (doc) => {
- delete doc._id;
- return doc;
- } });
- assert(result[0]._id);
- assert.equal(result[0].otherName._id, undefined);
- assert.equal(result[0].foo[0]._id, undefined);
- assert.equal(result[0].foo[1]._id, undefined);
- const single = await Test.findOne().lean({ transform: (doc) => {
- delete doc._id;
- return doc;
- } });
- assert(single._id);
- assert.equal(single.otherName._id, undefined);
- assert.equal(single.foo[0]._id, undefined);
- assert.equal(single.foo[0]._id, undefined);
+
+ const result = await Test.find().lean({
+ transform: (doc) => {
+ delete doc._id;
+ return doc;
+ }
+ });
+ assert.strictEqual(result[0]._id, undefined);
+ assert.strictEqual(result[0].otherName._id, undefined);
+ assert.strictEqual(result[0].foo[0]._id, undefined);
+ assert.strictEqual(result[0].foo[1]._id, undefined);
+
+ const single = await Test.findOne().lean({
+ transform: (doc) => {
+ delete doc._id;
+ return doc;
+ }
+ });
+ assert.strictEqual(single._id, undefined);
+ assert.strictEqual(single.otherName._id, undefined);
+ assert.strictEqual(single.foo[0]._id, undefined);
+ assert.strictEqual(single.foo[0]._id, undefined);
});
it('skips applying default projections over slice projections (gh-11940)', async function() {
diff --git a/test/schema.test.js b/test/schema.test.js
index 7bc6299e23e..56c55fe8964 100644
--- a/test/schema.test.js
+++ b/test/schema.test.js
@@ -924,6 +924,19 @@ describe('schema', function() {
assert.equal(called, true);
});
+
+ it('options param (gh-12077)', function() {
+ const Tobi = new Schema();
+ let called = false;
+
+ Tobi.plugin(function(schema, opts) {
+ assert.equal(schema, Tobi);
+ assert.deepStrictEqual(opts, { answer: 42 });
+ called = true;
+ }, { answer: 42 });
+
+ assert.equal(called, true);
+ });
});
describe('options', function() {
@@ -2792,4 +2805,14 @@ describe('schema', function() {
});
}, /Cannot use schema-level projections.*subdocument_mapping.not_selected/);
});
+
+ it('disallows setting special properties with `add()` or constructor (gh-12085)', async function() {
+ const maliciousPayload = '{"__proto__.toString": "Number"}';
+
+ assert.throws(() => {
+ mongoose.Schema(JSON.parse(maliciousPayload));
+ }, /__proto__/);
+
+ assert.ok({}.toString());
+ });
});
diff --git a/test/types/PipelineStage.test.ts b/test/types/PipelineStage.test.ts
index 37968a3b366..67dc3078185 100644
--- a/test/types/PipelineStage.test.ts
+++ b/test/types/PipelineStage.test.ts
@@ -415,3 +415,11 @@ const stages4: PipelineStage[] = [
}
}
];
+
+(function gh12096() {
+ const data: PipelineStage.AddFields = {
+ $addFields: {
+ name: { $meta: 'Bill' }
+ }
+ };
+})();
diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts
index 7a881947f7c..7a6550f7abf 100644
--- a/test/types/queries.test.ts
+++ b/test/types/queries.test.ts
@@ -325,5 +325,19 @@ function gh11964() {
/* ... */
}
}
+}
+function gh12091() {
+ interface IUser{
+ friendsNames: string[];
+ }
+ const userSchema = new Schema({
+ friendsNames: [String]
+ });
+
+ const update: UpdateQuery = { $addToSet: { friendsNames: 'John Doe' } };
+ if (!update?.$addToSet) {
+ return;
+ }
+ update.$addToSet.friendsNames = 'Jane Doe';
}
diff --git a/types/expressions.d.ts b/types/expressions.d.ts
index 69b882b2eb6..bce492cfe5d 100644
--- a/types/expressions.d.ts
+++ b/types/expressions.d.ts
@@ -2449,7 +2449,7 @@ declare module 'mongoose' {
FunctionExpression |
ObjectIdExpression |
ConditionalExpressionOperator |
- Expression.Let;
+ any;
export type ObjectIdExpression =
TypeExpressionOperatorReturningObjectId;
diff --git a/types/index.d.ts b/types/index.d.ts
index 8b7a63bbc41..3cd944d0d8f 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -437,6 +437,10 @@ declare module 'mongoose' {
export type SortOrder = -1 | 1 | 'asc' | 'ascending' | 'desc' | 'descending';
+ type Mutable = {
+ -readonly [K in keyof T]: T[K];
+ };
+
type _UpdateQuery = {
/** @see https://docs.mongodb.com/manual/reference/operator/update-field/ */
$currentDate?: AnyKeys & AnyObject;
@@ -450,10 +454,10 @@ declare module 'mongoose' {
$unset?: AnyKeys & AnyObject;
/** @see https://docs.mongodb.com/manual/reference/operator/update-array/ */
- $addToSet?: mongodb.SetFields;
+ $addToSet?: Mutable>;
$pop?: AnyKeys & AnyObject;
- $pull?: mongodb.PullOperator;
- $push?: mongodb.PushOperator;
+ $pull?: Mutable>;
+ $push?: Mutable>;
$pullAll?: mongodb.PullAllOperator;
/** @see https://docs.mongodb.com/manual/reference/operator/update-bitwise/ */