From 97e5a0eab8920b6be8cd3a9f02f4a0e74d32698c Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 22 Dec 2022 12:43:46 -0500 Subject: [PATCH 01/18] fix(model): respect discriminators with `Model.validate()` Fix #12621 --- lib/model.js | 7 +- test/model.test.js | 113 ---------------------------- test/model.validate.test.js | 146 ++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 114 deletions(-) create mode 100644 test/model.validate.test.js diff --git a/lib/model.js b/lib/model.js index 51d33bf5a41..a146eea1ef0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -36,6 +36,7 @@ const assignVals = require('./helpers/populate/assignVals'); const castBulkWrite = require('./helpers/model/castBulkWrite'); const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter'); const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult'); +const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue'); const discriminator = require('./helpers/model/discriminator'); const firstKey = require('./helpers/firstKey'); const each = require('./helpers/each'); @@ -4474,7 +4475,11 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) { } return this.db.base._promiseOrCallback(callback, cb => { - const schema = this.schema; + let schema = this.schema; + const discriminatorKey = schema.options.discriminatorKey; + if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) { + schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema; + } let paths = Object.keys(schema.paths); if (pathsToValidate != null) { diff --git a/test/model.test.js b/test/model.test.js index 6157f53e3b5..fb48340f063 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -7456,119 +7456,6 @@ describe('Model', function() { }); }); - it('Model.validate() (gh-7587)', async function() { - const Model = db.model('Test', new Schema({ - name: { - first: { - type: String, - required: true - }, - last: { - type: String, - required: true - } - }, - age: { - type: Number, - required: true - }, - comments: [{ name: { type: String, required: true } }] - })); - - - let err = null; - let obj = null; - - err = await Model.validate({ age: null }, ['age']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['age']); - - err = await Model.validate({ name: {} }, ['name']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['name.first', 'name.last']); - - obj = { name: { first: 'foo' } }; - err = await Model.validate(obj, ['name']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['name.last']); - - obj = { comments: [{ name: 'test' }, {}] }; - err = await Model.validate(obj, ['comments']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['comments.name']); - - obj = { age: '42' }; - await Model.validate(obj, ['age']); - assert.strictEqual(obj.age, 42); - }); - - it('Model.validate(...) validates paths in arrays (gh-8821)', async function() { - const userSchema = new Schema({ - friends: [{ type: String, required: true, minlength: 3 }] - }); - - const User = db.model('User', userSchema); - - const err = await User.validate({ friends: [null, 'A'] }).catch(err => err); - - assert.ok(err.errors['friends.0']); - assert.ok(err.errors['friends.1']); - - }); - - it('Model.validate() works with arrays (gh-10669)', async function() { - const testSchema = new Schema({ - docs: [String] - }); - - const Test = db.model('Test', testSchema); - - const test = { docs: ['6132655f2cdb9d94eaebc09b'] }; - - const err = await Test.validate(test); - assert.ifError(err); - }); - - it('Model.validate(...) uses document instance as context by default (gh-10132)', async function() { - const userSchema = new Schema({ - name: { - type: String, - required: function() { - return this.nameRequired; - } - }, - nameRequired: Boolean - }); - - const User = db.model('User', userSchema); - - const user = new User({ name: 'test', nameRequired: false }); - const err = await User.validate(user).catch(err => err); - - assert.ifError(err); - - }); - it('Model.validate(...) uses object as context by default (gh-10346)', async() => { - - const userSchema = new mongoose.Schema({ - name: { type: String, required: true }, - age: { type: Number, required() {return this && this.name === 'John';} } - }); - - const User = db.model('User', userSchema); - - const err1 = await User.validate({ name: 'John' }).then(() => null, err => err); - assert.ok(err1); - - const err2 = await User.validate({ name: 'Sam' }).then(() => null, err => err); - assert.ok(err2 === null); - - }); - it('sets correct `Document#op` with `save()` (gh-8439)', function() { const schema = Schema({ name: String }); const ops = []; diff --git a/test/model.validate.test.js b/test/model.validate.test.js new file mode 100644 index 00000000000..877d83d90f0 --- /dev/null +++ b/test/model.validate.test.js @@ -0,0 +1,146 @@ +'use strict'; + +const start = require('./common'); + +const assert = require('assert'); + +const mongoose = start.mongoose; +const Schema = mongoose.Schema; + +describe('model: validate: ', function() { + afterEach(() => mongoose.deleteModel(/.*/)); + + it('Model.validate() (gh-7587)', async function() { + const Model = mongoose.model('Test', new Schema({ + name: { + first: { + type: String, + required: true + }, + last: { + type: String, + required: true + } + }, + age: { + type: Number, + required: true + }, + comments: [{ name: { type: String, required: true } }] + })); + + + let err = null; + let obj = null; + + err = await Model.validate({ age: null }, ['age']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['age']); + + err = await Model.validate({ name: {} }, ['name']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['name.first', 'name.last']); + + obj = { name: { first: 'foo' } }; + err = await Model.validate(obj, ['name']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['name.last']); + + obj = { comments: [{ name: 'test' }, {}] }; + err = await Model.validate(obj, ['comments']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['comments.name']); + + obj = { age: '42' }; + await Model.validate(obj, ['age']); + assert.strictEqual(obj.age, 42); + }); + + it('Model.validate(...) validates paths in arrays (gh-8821)', async function() { + const userSchema = new Schema({ + friends: [{ type: String, required: true, minlength: 3 }] + }); + + const User = mongoose.model('User', userSchema); + + const err = await User.validate({ friends: [null, 'A'] }).catch(err => err); + + assert.ok(err.errors['friends.0']); + assert.ok(err.errors['friends.1']); + }); + + it('Model.validate(...) respects discriminators (gh-12621)', async function() { + const CatSchema = new Schema({ meows: { type: Boolean, required: true } }); + const DogSchema = new Schema({ barks: { type: Boolean, required: true } }); + const AnimalSchema = new Schema( + { id: String }, + { discriminatorKey: 'kind' } + ); + AnimalSchema.discriminator('cat', CatSchema); + AnimalSchema.discriminator('dog', DogSchema); + + const Animal = mongoose.model('Test', AnimalSchema); + + const invalidPet1 = new Animal({ + id: '123', + kind: 'dog', + meows: true + }); + + const err = await Animal.validate(invalidPet1).then(() => null, err => err); + assert.ok(err); + assert.ok(err.errors['barks']); + }); + + it('Model.validate() works with arrays (gh-10669)', async function() { + const testSchema = new Schema({ + docs: [String] + }); + + const Test = mongoose.model('Test', testSchema); + + const test = { docs: ['6132655f2cdb9d94eaebc09b'] }; + + const err = await Test.validate(test); + assert.ifError(err); + }); + + it('Model.validate(...) uses document instance as context by default (gh-10132)', async function() { + const userSchema = new Schema({ + name: { + type: String, + required: function() { + return this.nameRequired; + } + }, + nameRequired: Boolean + }); + + const User = mongoose.model('User', userSchema); + + const user = new User({ name: 'test', nameRequired: false }); + const err = await User.validate(user).catch(err => err); + + assert.ifError(err); + + }); + it('Model.validate(...) uses object as context by default (gh-10346)', async() => { + + const userSchema = new mongoose.Schema({ + name: { type: String, required: true }, + age: { type: Number, required() {return this && this.name === 'John';} } + }); + + const User = mongoose.model('User', userSchema); + + const err1 = await User.validate({ name: 'John' }).then(() => null, err => err); + assert.ok(err1); + + const err2 = await User.validate({ name: 'Sam' }).then(() => null, err => err); + assert.ok(err2 === null); + }); +}); From bb874d9f236b11bff37e7be3cad1f811b44f254e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 22 Dec 2022 13:56:10 -0500 Subject: [PATCH 02/18] docs(typescript): make note about recommending `strict` mode when using auto typed schemas Re: #12420 --- docs/typescript/schemas.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/typescript/schemas.md b/docs/typescript/schemas.md index 656c058d5c6..80a86ed126d 100644 --- a/docs/typescript/schemas.md +++ b/docs/typescript/schemas.md @@ -1,11 +1,9 @@ # Schemas in TypeScript Mongoose [schemas](../guide.html) are how you tell Mongoose what your documents look like. -Mongoose schemas are separate from TypeScript interfaces, so you need to define both a _document interface_ and a _schema_ until V6.3.1. -Mongoose supports auto typed schemas so you don't need to define additional typescript interface anymore but you are still able to do so. -Mongoose provides a `InferSchemaType`, which infers the type of the auto typed schema document when needed. +Mongoose schemas are separate from TypeScript interfaces, so you need to either define both a _document interface_ and a _schema_; or rely on Mongoose to automatically infer the type from the schema definition. -`Until mongoose V6.3.1:` +### Separate document interface definition ```typescript import { Schema } from 'mongoose'; @@ -25,7 +23,12 @@ const schema = new Schema({ }); ``` -`another approach:` +By default, Mongoose does **not** check if your document interface lines up with your schema. +For example, the above code won't throw an error if `email` is optional in the document interface, but `required` in `schema`. + +### Automatic type inference + +Mongoose can also automatically infer the document type from your schema definition as follows. ```typescript import { Schema, InferSchemaType } from 'mongoose'; @@ -53,11 +56,16 @@ type User = InferSchemaType; // avatar?: string; // } - +// `UserModel` will have `name: string`, etc. +const UserModel = mongoose.model('User', schema); ``` -By default, Mongoose does **not** check if your document interface lines up with your schema. -For example, the above code won't throw an error if `email` is optional in the document interface, but `required` in `schema`. +There are a few caveats for using automatic type inference: + +1. You need to set `strict: true` in your `tsconfig.json` or `--strict` if running `tsc`. There are [known issues](https://github.com/Automattic/mongoose/issues/12420) with automatic type inference with strict mode disabled. +2. You need to define your schema in the `new Schema()` call. Don't assign your schema definition to a temporary variable. Doing something like `const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition);` will not work. + +If automatic type inference doesn't work for you, you can always fall back to document interface definitions. ## Generic parameters From 9f09f92b5fe524464637bc3f92a206c31fe9844e Mon Sep 17 00:00:00 2001 From: hasezoey Date: Thu, 22 Dec 2022 20:08:34 +0100 Subject: [PATCH 03/18] test(model.validate): change "afterEach" to "beforeEach" --- test/model.validate.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/model.validate.test.js b/test/model.validate.test.js index 877d83d90f0..d58cb57ebcd 100644 --- a/test/model.validate.test.js +++ b/test/model.validate.test.js @@ -8,7 +8,7 @@ const mongoose = start.mongoose; const Schema = mongoose.Schema; describe('model: validate: ', function() { - afterEach(() => mongoose.deleteModel(/.*/)); + beforeEach(() => mongoose.deleteModel(/.*/)); it('Model.validate() (gh-7587)', async function() { const Model = mongoose.model('Test', new Schema({ From 1482a1d27bae4c1e8d1b8263f32ccb95b0d8b23b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 22 Dec 2022 21:32:46 -0500 Subject: [PATCH 04/18] fix(schema): propagate strictQuery to implicitly created schemas for embedded discriminators Fix #12796 --- lib/schema.js | 3 +++ test/schema.documentarray.test.js | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/schema.js b/lib/schema.js index 70558e06f24..0bd2a8aadcd 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -1250,6 +1250,9 @@ Schema.prototype.interpretAsType = function(path, obj, options) { if (options.hasOwnProperty('strict')) { childSchemaOptions.strict = options.strict; } + if (options.hasOwnProperty('strictQuery')) { + childSchemaOptions.strictQuery = options.strictQuery; + } if (this._userProvidedOptions.hasOwnProperty('_id')) { childSchemaOptions._id = this._userProvidedOptions._id; diff --git a/test/schema.documentarray.test.js b/test/schema.documentarray.test.js index 3217e165d88..d9ccb6c1f6b 100644 --- a/test/schema.documentarray.test.js +++ b/test/schema.documentarray.test.js @@ -75,6 +75,15 @@ describe('schema.documentarray', function() { done(); }); + it('propagates strictQuery to implicitly created schemas (gh-12796)', function() { + const schema = new Schema({ + arr: [{ name: String }] + }, { strictQuery: 'throw' }); + + assert.equal(schema.childSchemas.length, 1); + assert.equal(schema.childSchemas[0].schema.options.strictQuery, 'throw'); + }); + it('supports set with array of document arrays (gh-7799)', function() { const subSchema = new Schema({ title: String From 730abafc214d74c2bc6dac43a71bf14f11253df8 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 23 Dec 2022 10:51:26 -0500 Subject: [PATCH 05/18] test(model): make timeseries test more durable by avoiding autoIndex and drop/init race Fix #12643 --- test/model.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/model.test.js b/test/model.test.js index 6157f53e3b5..5728e286114 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -6916,10 +6916,12 @@ describe('Model', function() { granularity: 'hours' }, autoCreate: false, + autoIndex: false, expireAfterSeconds: 86400 }); - const Test = db.model('Test', schema); + const Test = db.model('Test', schema, 'Test'); + await Test.init(); await Test.collection.drop().catch(() => {}); await Test.createCollection(); From 8d8d7a7a017abcd3aa495bff81afc3f7d0143442 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 23 Dec 2022 10:57:28 -0500 Subject: [PATCH 06/18] Update model.validate.test.js --- test/model.validate.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/model.validate.test.js b/test/model.validate.test.js index d58cb57ebcd..877d83d90f0 100644 --- a/test/model.validate.test.js +++ b/test/model.validate.test.js @@ -8,7 +8,7 @@ const mongoose = start.mongoose; const Schema = mongoose.Schema; describe('model: validate: ', function() { - beforeEach(() => mongoose.deleteModel(/.*/)); + afterEach(() => mongoose.deleteModel(/.*/)); it('Model.validate() (gh-7587)', async function() { const Model = mongoose.model('Test', new Schema({ From ad2446c367dd5d1c5e21d7f6428d001744a095f6 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 23 Dec 2022 11:40:51 -0500 Subject: [PATCH 07/18] docs: store content with associated version re: #12548 --- docs/search.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/search.js b/docs/search.js index 8aa7d4c6455..83615f0ca96 100644 --- a/docs/search.js +++ b/docs/search.js @@ -6,6 +6,7 @@ const filemap = require('./source'); const fs = require('fs'); const pug = require('pug'); const mongoose = require('../'); +let { version } = require('../package.json'); const { marked: markdown } = require('marked'); const highlight = require('highlight.js'); @@ -15,10 +16,14 @@ markdown.setOptions({ } }); +// 5.13.5 -> 5.x, 6.8.2 -> 6.x, etc. +version = version.slice(0, version.indexOf('.')) + '.x'; + const contentSchema = new mongoose.Schema({ title: { type: String, required: true }, body: { type: String, required: true }, - url: { type: String, required: true } + url: { type: String, required: true }, + version: { type: String, required: true, default: version } }); contentSchema.index({ title: 'text', body: 'text' }); const Content = mongoose.model('Content', contentSchema, 'Content'); @@ -28,7 +33,6 @@ const files = Object.keys(filemap); for (const filename of files) { const file = filemap[filename]; - console.log(file) if (file.api) { // API docs are special, raw content is in the `docs` property for (const _class of file.docs) { @@ -115,8 +119,11 @@ run().catch(error => console.error(error.stack)); async function run() { await mongoose.connect(config.uri, { dbName: 'mongoose' }); - await Content.deleteMany({}); + await Content.deleteMany({ version }); for (const content of contents) { + if (version !== '6.x') { + content.url = `/docs/${version}/docs/${content.url}`; + } await content.save(); } From 3f675a93612b279543b90c4ed30d5abbcb9245d3 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Fri, 23 Dec 2022 18:22:39 +0100 Subject: [PATCH 08/18] test(query.test): add write-concern option in hopes this fixes some inconsistent tests in replica set mode --- test/query.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/query.test.js b/test/query.test.js index a02a37896d0..3d1220a0cc4 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -1564,7 +1564,7 @@ describe('Query', function() { const Product = db.model('Product', productSchema); Product.create( { numbers: [3, 4, 5] }, - { strings: 'hi there'.split(' ') }, function(err, doc1, doc2) { + { strings: 'hi there'.split(' '), w: 'majority' }, function(err, doc1, doc2) { assert.ifError(err); Product.find().setOptions({ limit: 1, sort: { _id: -1 }, read: 'n' }).exec(function(err, docs) { assert.ifError(err); From a4bd7cc91182abea73bb4570213afcc22bbcdf19 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 23 Dec 2022 12:46:45 -0500 Subject: [PATCH 09/18] docs: search by version in docs --- docs/js/search.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/js/search.js b/docs/js/search.js index 71c623502e2..2d62ed66901 100644 --- a/docs/js/search.js +++ b/docs/js/search.js @@ -9,9 +9,12 @@ for (var i = 0; i < pairs.length; ++i) { } } +var versionFromUrl = window.location.pathname.match(/^\/docs\/(\d+\.x)/); +var version = versionFromUrl ? versionFromUrl[1] : '6.x'; + if (q != null) { document.getElementById('search-input').value = decodeURIComponent(q); - fetch(root + '/search?search=' + q). + fetch(root + '/search?search=' + q + '&version=' + version). then(function(res) { return res.json(); }). then( function(result) { From c409b2c25584438a38e2498b2b100b89769b3a04 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sat, 24 Dec 2022 13:06:42 -0500 Subject: [PATCH 10/18] fix(discriminator): apply built-in plugins to discriminator schema even if `mergeHooks` and `mergePlugins` are both false Fix #12696 --- lib/helpers/model/discriminator.js | 3 +++ lib/helpers/schema/applyBuiltinPlugins.js | 12 ++++++++++ lib/plugins/clearValidating.js | 28 ----------------------- lib/plugins/index.js | 7 ++++++ test/model.discriminator.test.js | 24 +++++++++++++++++++ 5 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 lib/helpers/schema/applyBuiltinPlugins.js delete mode 100644 lib/plugins/clearValidating.js create mode 100644 lib/plugins/index.js diff --git a/lib/helpers/model/discriminator.js b/lib/helpers/model/discriminator.js index 96443002ab1..0c843df10da 100644 --- a/lib/helpers/model/discriminator.js +++ b/lib/helpers/model/discriminator.js @@ -1,6 +1,7 @@ 'use strict'; const Mixed = require('../../schema/mixed'); +const applyBuiltinPlugins = require('../schema/applyBuiltinPlugins'); const defineKey = require('../document/compile').defineKey; const get = require('../get'); const utils = require('../../utils'); @@ -40,6 +41,8 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu model.base._applyPlugins(schema, { skipTopLevel: !applyPluginsToDiscriminators }); + } else if (!mergeHooks) { + applyBuiltinPlugins(schema); } const key = model.schema.options.discriminatorKey; diff --git a/lib/helpers/schema/applyBuiltinPlugins.js b/lib/helpers/schema/applyBuiltinPlugins.js new file mode 100644 index 00000000000..cbf999dd01d --- /dev/null +++ b/lib/helpers/schema/applyBuiltinPlugins.js @@ -0,0 +1,12 @@ +'use strict'; + +const builtinPlugins = require('../../plugins'); + +module.exports = function applyBuiltinPlugins(schema) { + for (const plugin of Object.values(builtinPlugins)) { + plugin(schema, { deduplicate: true }); + } + schema.plugins = Object.values(builtinPlugins). + map(fn => ({ fn, opts: { deduplicate: true } })). + concat(schema.plugins); +}; \ No newline at end of file diff --git a/lib/plugins/clearValidating.js b/lib/plugins/clearValidating.js deleted file mode 100644 index 50264e33a6b..00000000000 --- a/lib/plugins/clearValidating.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -/*! - * ignore - */ - -module.exports = function clearValidating(schema) { - // `this.$__.validating` tracks whether there are multiple validations running - // in parallel. We need to clear `this.$__.validating` before post hooks for gh-8597 - const unshift = true; - schema.s.hooks.post('validate', false, function clearValidatingPostValidate() { - if (this.$isSubdocument) { - return; - } - - this.$__.validating = null; - }, unshift); - - schema.s.hooks.post('validate', false, function clearValidatingPostValidateError(error, res, next) { - if (this.$isSubdocument) { - next(); - return; - } - - this.$__.validating = null; - next(); - }, unshift); -}; diff --git a/lib/plugins/index.js b/lib/plugins/index.js new file mode 100644 index 00000000000..69fa6ad284c --- /dev/null +++ b/lib/plugins/index.js @@ -0,0 +1,7 @@ +'use strict'; + +exports.removeSubdocs = require('./removeSubdocs'); +exports.saveSubdocs = require('./saveSubdocs'); +exports.sharding = require('./sharding'); +exports.trackTransaction = require('./trackTransaction'); +exports.validateBeforeSave = require('./validateBeforeSave'); diff --git a/test/model.discriminator.test.js b/test/model.discriminator.test.js index 3e34bfa7b62..a7eb5469a32 100644 --- a/test/model.discriminator.test.js +++ b/test/model.discriminator.test.js @@ -2077,4 +2077,28 @@ describe('model', function() { schema.pre('save', function testHook12604() {}); } }); + + it('applies built-in plugins if mergePlugins and mergeHooks disabled (gh-12696) (gh-12604)', async function() { + const shapeDef = { name: String }; + const shapeSchema = Schema(shapeDef, { discriminatorKey: 'kind' }); + + const Shape = db.model('Test', shapeSchema); + + let subdocSaveCalls = 0; + const nestedSchema = Schema({ test: String }); + nestedSchema.pre('save', function() { + ++subdocSaveCalls; + }); + + const squareSchema = Schema({ ...shapeDef, nested: nestedSchema }); + const Square = Shape.discriminator( + 'Square', + squareSchema, + { mergeHooks: false, mergePlugins: false } + ); + + assert.equal(subdocSaveCalls, 0); + await Square.create({ nested: { test: 'foo' } }); + assert.equal(subdocSaveCalls, 1); + }); }); From e64f652af89a086963797278d5957490b02f917d Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Dec 2022 17:05:40 -0500 Subject: [PATCH 11/18] Update lib/helpers/schema/applyBuiltinPlugins.js Co-authored-by: hasezoey --- lib/helpers/schema/applyBuiltinPlugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers/schema/applyBuiltinPlugins.js b/lib/helpers/schema/applyBuiltinPlugins.js index cbf999dd01d..8bd7319cbb1 100644 --- a/lib/helpers/schema/applyBuiltinPlugins.js +++ b/lib/helpers/schema/applyBuiltinPlugins.js @@ -9,4 +9,4 @@ module.exports = function applyBuiltinPlugins(schema) { schema.plugins = Object.values(builtinPlugins). map(fn => ({ fn, opts: { deduplicate: true } })). concat(schema.plugins); -}; \ No newline at end of file +}; From ffefa87c895287826e3f39178b3f6e9492121e9e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Dec 2022 17:09:28 -0500 Subject: [PATCH 12/18] refactor: use builtinPlugins list instead of importing all plugins --- lib/helpers/schema/applyBuiltinPlugins.js | 2 +- lib/index.js | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/helpers/schema/applyBuiltinPlugins.js b/lib/helpers/schema/applyBuiltinPlugins.js index cbf999dd01d..8bd7319cbb1 100644 --- a/lib/helpers/schema/applyBuiltinPlugins.js +++ b/lib/helpers/schema/applyBuiltinPlugins.js @@ -9,4 +9,4 @@ module.exports = function applyBuiltinPlugins(schema) { schema.plugins = Object.values(builtinPlugins). map(fn => ({ fn, opts: { deduplicate: true } })). concat(schema.plugins); -}; \ No newline at end of file +}; diff --git a/lib/index.js b/lib/index.js index 0b76506cb0e..2109d3eec6d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -19,21 +19,17 @@ const Types = require('./types'); const Query = require('./query'); const Model = require('./model'); const applyPlugins = require('./helpers/schema/applyPlugins'); +const builtinPlugins = require('./plugins'); const driver = require('./driver'); const promiseOrCallback = require('./helpers/promiseOrCallback'); const legacyPluralize = require('./helpers/pluralize'); const utils = require('./utils'); const pkg = require('../package.json'); const cast = require('./cast'); -const removeSubdocs = require('./plugins/removeSubdocs'); -const saveSubdocs = require('./plugins/saveSubdocs'); -const trackTransaction = require('./plugins/trackTransaction'); -const validateBeforeSave = require('./plugins/validateBeforeSave'); const Aggregate = require('./aggregate'); const PromiseProvider = require('./promise_provider'); const printStrictQueryWarning = require('./helpers/printStrictQueryWarning'); -const shardingPlugin = require('./plugins/sharding'); const trusted = require('./helpers/query/trusted').trusted; const sanitizeFilter = require('./helpers/query/sanitizeFilter'); const isBsonType = require('./helpers/isBsonType'); @@ -108,13 +104,7 @@ function Mongoose(options) { configurable: false, enumerable: true, writable: false, - value: [ - [saveSubdocs, { deduplicate: true }], - [validateBeforeSave, { deduplicate: true }], - [shardingPlugin, { deduplicate: true }], - [removeSubdocs, { deduplicate: true }], - [trackTransaction, { deduplicate: true }] - ] + value: Object.values(builtinPlugins).map(plugin => ([plugin, { deduplicate: true }])) }); } From 532b4521b257da72c04921ecf6e0db2b9c1a6adb Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Dec 2022 17:32:25 -0500 Subject: [PATCH 13/18] docs: make not about strictNullChecks --- docs/typescript/schemas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/typescript/schemas.md b/docs/typescript/schemas.md index 80a86ed126d..6640427c8b9 100644 --- a/docs/typescript/schemas.md +++ b/docs/typescript/schemas.md @@ -62,7 +62,7 @@ const UserModel = mongoose.model('User', schema); There are a few caveats for using automatic type inference: -1. You need to set `strict: true` in your `tsconfig.json` or `--strict` if running `tsc`. There are [known issues](https://github.com/Automattic/mongoose/issues/12420) with automatic type inference with strict mode disabled. +1. You need to set `strictNullChecks: true` or `strict: true` in your `tsconfig.json`. Or, if you're setting flags at the command line, `--strictNullChecks` or `--strict`. There are [known issues](https://github.com/Automattic/mongoose/issues/12420) with automatic type inference with strict mode disabled. 2. You need to define your schema in the `new Schema()` call. Don't assign your schema definition to a temporary variable. Doing something like `const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition);` will not work. If automatic type inference doesn't work for you, you can always fall back to document interface definitions. From f140bf2c1d788528f2e8c54d52c12d643c9ecdfa Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Dec 2022 17:43:48 -0500 Subject: [PATCH 14/18] test: try clearing models before and after --- test/model.validate.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/model.validate.test.js b/test/model.validate.test.js index 877d83d90f0..30891867ff3 100644 --- a/test/model.validate.test.js +++ b/test/model.validate.test.js @@ -8,7 +8,8 @@ const mongoose = start.mongoose; const Schema = mongoose.Schema; describe('model: validate: ', function() { - afterEach(() => mongoose.deleteModel(/.*/)); + beforeEach(() => mongoose.deleteModel(/.*/)); + after(() => mongoose.deleteModel(/.*/)); it('Model.validate() (gh-7587)', async function() { const Model = mongoose.model('Test', new Schema({ From fd1fa9d83d660adbebac1b2b1eedc57fbd85b39e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Dec 2022 17:01:19 -0500 Subject: [PATCH 15/18] docs: backport #12830 to 5.x --- docs/js/search.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/js/search.js b/docs/js/search.js index 71c623502e2..2d62ed66901 100644 --- a/docs/js/search.js +++ b/docs/js/search.js @@ -9,9 +9,12 @@ for (var i = 0; i < pairs.length; ++i) { } } +var versionFromUrl = window.location.pathname.match(/^\/docs\/(\d+\.x)/); +var version = versionFromUrl ? versionFromUrl[1] : '6.x'; + if (q != null) { document.getElementById('search-input').value = decodeURIComponent(q); - fetch(root + '/search?search=' + q). + fetch(root + '/search?search=' + q + '&version=' + version). then(function(res) { return res.json(); }). then( function(result) { From c3384bcd1dc60fa15b57f41931bac9d9c99fc6e2 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Dec 2022 17:19:21 -0500 Subject: [PATCH 16/18] docs: fix search re: #12830 --- docs/js/navbar-search.js | 25 ++++++++++++++++--------- docs/js/search.js | 3 ++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/js/navbar-search.js b/docs/js/navbar-search.js index 94256ad730e..e80f5a4d48b 100644 --- a/docs/js/navbar-search.js +++ b/docs/js/navbar-search.js @@ -1,11 +1,18 @@ -document.getElementById('search-button-nav').onclick = function() { - var q = document.getElementById('search-input-nav').value; - window.location.href = 'search.html?q=' + encodeURIComponent(q); -}; +(function() { + var versionFromUrl = window.location.pathname.match(/^\/docs\/(\d+\.x)/); + var version = versionFromUrl ? versionFromUrl[1] : defaultVersion; -var q = document.getElementById('search-input-nav').onkeyup = function(ev) { - if (ev.keyCode === 13) { + var searchPrefix = versionFromUrl ? '/docs/' + version + '/docs/' : '/docs/'; + + document.getElementById('search-button-nav').onclick = function() { var q = document.getElementById('search-input-nav').value; - window.location.href = '/docs/search.html?q=' + encodeURIComponent(q); - } -}; \ No newline at end of file + window.location.href = searchPrefix + 'search.html?q=' + encodeURIComponent(q); + }; + + document.getElementById('search-input-nav').onkeyup = function(ev) { + if (ev.keyCode === 13) { + var q = document.getElementById('search-input-nav').value; + window.location.href = searchPrefix + '/search.html?q=' + encodeURIComponent(q); + } + }; +})(); \ No newline at end of file diff --git a/docs/js/search.js b/docs/js/search.js index 2d62ed66901..dc7c33ea8fc 100644 --- a/docs/js/search.js +++ b/docs/js/search.js @@ -9,8 +9,9 @@ for (var i = 0; i < pairs.length; ++i) { } } +var defaultVersion = '6.x'; var versionFromUrl = window.location.pathname.match(/^\/docs\/(\d+\.x)/); -var version = versionFromUrl ? versionFromUrl[1] : '6.x'; +var version = versionFromUrl ? versionFromUrl[1] : defaultVersion; if (q != null) { document.getElementById('search-input').value = decodeURIComponent(q); From 48179b42b080712b572afd1aff3bd313937bcc92 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Dec 2022 17:34:45 -0500 Subject: [PATCH 17/18] chore: search fixes --- docs/js/search.js | 2 +- docs/search.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/js/search.js b/docs/js/search.js index dc7c33ea8fc..c3ad6cbcc96 100644 --- a/docs/js/search.js +++ b/docs/js/search.js @@ -26,7 +26,7 @@ if (q != null) { var html = ''; for (var i = 0; i < result.results.length; ++i) { var res = result.results[i]; - var url = res.url.replace(/^\//, ''); + var url = res.url; html += '
  • ' + '' + res.title + diff --git a/docs/search.js b/docs/search.js index ca18f2bae13..03de4f2f32d 100644 --- a/docs/search.js +++ b/docs/search.js @@ -122,7 +122,8 @@ async function run() { await Content.deleteMany({ version }); for (const content of contents) { if (version !== '6.x') { - content.url = `/docs/${version}/docs${content.url}`; + const url = content.url.startsWith('/') ? content.url : `/${content.url}`; + content.url = `/docs/${version}/docs${url}`; } await content.save(); } From aef4c7bbefcc5cc44fc8e2ea51ef7030df86108a Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Dec 2022 21:35:40 -0500 Subject: [PATCH 18/18] docs: quick fix for search re: #12830 --- docs/search.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/search.js b/docs/search.js index 03de4f2f32d..08fef2d1424 100644 --- a/docs/search.js +++ b/docs/search.js @@ -121,7 +121,13 @@ async function run() { await Content.deleteMany({ version }); for (const content of contents) { - if (version !== '6.x') { + if (version === '6.x') { + let url = content.url.startsWith('/') ? content.url : `/${content.url}`; + if (!url.startsWith('/docs')) { + url = '/docs' + url; + } + content.url = url; + } else { const url = content.url.startsWith('/') ? content.url : `/${content.url}`; content.url = `/docs/${version}/docs${url}`; }