From cfb2785a975bc24eb88f01883605380e365eafc3 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 21 Nov 2022 10:03:02 -0800 Subject: [PATCH 01/24] fix(types): avoid typeof Query with generics for TypeScript 4.6 support Fix #12688 --- types/query.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/query.d.ts b/types/query.d.ts index 3a3a7a6b36f..5dec26efd7a 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -616,7 +616,7 @@ declare module 'mongoose' { then: Promise['then']; /** Converts this query to a customized, reusable query constructor with all arguments and options retained. */ - toConstructor(): typeof Query; + toConstructor(): RetType; /** Declare and/or execute this query as an update() operation. */ update(filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers; From aa06f1d25673cbf2bd6bf683ae24f944cece0239 Mon Sep 17 00:00:00 2001 From: Eliott C Date: Tue, 22 Nov 2022 16:45:50 +0100 Subject: [PATCH 02/24] fix(types): Update replaceWith pipeline stage --- types/pipelinestage.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/pipelinestage.d.ts b/types/pipelinestage.d.ts index f58e4b61f27..c3ee2f0a168 100644 --- a/types/pipelinestage.d.ts +++ b/types/pipelinestage.d.ts @@ -213,7 +213,7 @@ declare module 'mongoose' { export interface ReplaceWith { /** [`$replaceWith` reference](https://docs.mongodb.com/manual/reference/operator/aggregation/replaceWith/) */ - $replaceWith: ObjectExpressionOperator | { [field: string]: Expression }; + $replaceWith: ObjectExpressionOperator | { [field: string]: Expression } | `${string}`; } export interface Sample { From 616ce5dedf47a1ece96161a3b714e4c3ebbbad15 Mon Sep 17 00:00:00 2001 From: Eliott C Date: Tue, 22 Nov 2022 16:48:07 +0100 Subject: [PATCH 03/24] Correctly prefix with $ --- types/pipelinestage.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/pipelinestage.d.ts b/types/pipelinestage.d.ts index c3ee2f0a168..37d228ff1b7 100644 --- a/types/pipelinestage.d.ts +++ b/types/pipelinestage.d.ts @@ -213,7 +213,7 @@ declare module 'mongoose' { export interface ReplaceWith { /** [`$replaceWith` reference](https://docs.mongodb.com/manual/reference/operator/aggregation/replaceWith/) */ - $replaceWith: ObjectExpressionOperator | { [field: string]: Expression } | `${string}`; + $replaceWith: ObjectExpressionOperator | { [field: string]: Expression } | `$${string}`; } export interface Sample { From fde5e0f4b973713e52253eebaa273822f8d13ba0 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 22 Nov 2022 15:34:10 -0500 Subject: [PATCH 04/24] fix(cursor): make `eachAsync()` avoid modifying batch when mixing `parallel` and `batchSize` Fix #12652 --- lib/helpers/cursor/eachAsync.js | 144 ++++++++++++++------------ test/helpers/cursor.eachAsync.test.js | 24 +++++ 2 files changed, 103 insertions(+), 65 deletions(-) diff --git a/lib/helpers/cursor/eachAsync.js b/lib/helpers/cursor/eachAsync.js index 735fd2d8156..e3f6f8a24f4 100644 --- a/lib/helpers/cursor/eachAsync.js +++ b/lib/helpers/cursor/eachAsync.js @@ -33,7 +33,7 @@ module.exports = function eachAsync(next, fn, options, callback) { const aggregatedErrors = []; const enqueue = asyncQueue(); - let drained = false; + let aborted = false; return promiseOrCallback(callback, cb => { if (signal != null) { @@ -42,7 +42,7 @@ module.exports = function eachAsync(next, fn, options, callback) { } signal.addEventListener('abort', () => { - drained = true; + aborted = true; return cb(null); }, { once: true }); } @@ -63,90 +63,104 @@ module.exports = function eachAsync(next, fn, options, callback) { function iterate(finalCallback) { let handleResultsInProgress = 0; let currentDocumentIndex = 0; - let documentsBatch = []; let error = null; for (let i = 0; i < parallel; ++i) { - enqueue(fetch); + enqueue(createFetch()); } - function fetch(done) { - if (drained || error) { - return done(); - } + function createFetch() { + let documentsBatch = []; + let drained = false; + + return fetch; - next(function(err, doc) { - if (drained || error != null) { + function fetch(done) { + if (drained || aborted) { + return done(); + } else if (error) { return done(); } - if (err != null) { - if (continueOnError) { - aggregatedErrors.push(err); - } else { - error = err; - finalCallback(err); + + next(function(err, doc) { + if (error != null) { return done(); } - } - if (doc == null) { - drained = true; - if (handleResultsInProgress <= 0) { - const finalErr = continueOnError ? - createEachAsyncMultiError(aggregatedErrors) : - error; - - finalCallback(finalErr); - } else if (batchSize && documentsBatch.length) { - handleNextResult(documentsBatch, currentDocumentIndex++, handleNextResultCallBack); + if (err != null) { + if (err.name === 'MongoCursorExhaustedError') { + // We may end up calling `next()` multiple times on an exhausted + // cursor, which leads to an error. In case cursor is exhausted, + // just treat it as if the cursor returned no document, which is + // how a cursor indicates it is exhausted. + doc = null; + } else if (continueOnError) { + aggregatedErrors.push(err); + } else { + error = err; + finalCallback(err); + return done(); + } + } + if (doc == null) { + drained = true; + if (handleResultsInProgress <= 0) { + const finalErr = continueOnError ? + createEachAsyncMultiError(aggregatedErrors) : + error; + + finalCallback(finalErr); + } else if (batchSize && documentsBatch.length) { + handleNextResult(documentsBatch, currentDocumentIndex++, handleNextResultCallBack); + } + return done(); } - return done(); - } - ++handleResultsInProgress; + ++handleResultsInProgress; - // Kick off the subsequent `next()` before handling the result, but - // make sure we know that we still have a result to handle re: #8422 - immediate(() => done()); + // Kick off the subsequent `next()` before handling the result, but + // make sure we know that we still have a result to handle re: #8422 + immediate(() => done()); - if (batchSize) { - documentsBatch.push(doc); - } + if (batchSize) { + documentsBatch.push(doc); + } - // If the current documents size is less than the provided patch size don't process the documents yet - if (batchSize && documentsBatch.length !== batchSize) { - immediate(() => enqueue(fetch)); - return; - } + // If the current documents size is less than the provided batch size don't process the documents yet + if (batchSize && documentsBatch.length !== batchSize) { + immediate(() => enqueue(fetch)); + return; + } - const docsToProcess = batchSize ? documentsBatch : doc; + const docsToProcess = batchSize ? documentsBatch : doc; - function handleNextResultCallBack(err) { - if (batchSize) { - handleResultsInProgress -= documentsBatch.length; - documentsBatch = []; - } else { - --handleResultsInProgress; - } - if (err != null) { - if (continueOnError) { - aggregatedErrors.push(err); + function handleNextResultCallBack(err) { + if (batchSize) { + handleResultsInProgress -= documentsBatch.length; + documentsBatch = []; } else { - error = err; - return finalCallback(err); + --handleResultsInProgress; + } + if (err != null) { + if (continueOnError) { + aggregatedErrors.push(err); + } else { + error = err; + return finalCallback(err); + } + } + if ((drained || aborted) && handleResultsInProgress <= 0) { + const finalErr = continueOnError ? + createEachAsyncMultiError(aggregatedErrors) : + error; + return finalCallback(finalErr); } - } - if (drained && handleResultsInProgress <= 0) { - const finalErr = continueOnError ? - createEachAsyncMultiError(aggregatedErrors) : - error; - return finalCallback(finalErr); - } - immediate(() => enqueue(fetch)); - } + immediate(() => enqueue(fetch)); + } - handleNextResult(docsToProcess, currentDocumentIndex++, handleNextResultCallBack); - }); + handleNextResult(docsToProcess, currentDocumentIndex++, handleNextResultCallBack); + }); + } } } diff --git a/test/helpers/cursor.eachAsync.test.js b/test/helpers/cursor.eachAsync.test.js index b42cab85da0..57411ccfd5d 100644 --- a/test/helpers/cursor.eachAsync.test.js +++ b/test/helpers/cursor.eachAsync.test.js @@ -189,6 +189,30 @@ describe('eachAsync()', function() { assert.equal(numCalled, 1); }); + it('avoids mutating document batch with parallel (gh-12652)', async() => { + const max = 100; + let numCalled = 0; + function next(cb) { + setImmediate(() => { + if (++numCalled > max) { + return cb(null, null); + } + cb(null, { num: numCalled }); + }); + } + + let numDocsProcessed = 0; + async function fn(batch) { + numDocsProcessed += batch.length; + const length = batch.length; + await new Promise(resolve => setTimeout(resolve, 50)); + assert.equal(batch.length, length); + } + + await eachAsync(next, fn, { parallel: 7, batchSize: 10 }); + assert.equal(numDocsProcessed, max); + }); + it('using AbortSignal (gh-12173)', async function() { if (typeof AbortController === 'undefined') { return this.skip(); From e50de5c0f7f2e3d9386638f7b5aac027cdd153e1 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 22 Nov 2022 16:48:37 -0500 Subject: [PATCH 05/24] chore: release 6.7.3 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d54e3a7223e..17432a1ed80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +6.7.3 / 2022-11-22 +================== + * fix(document): handle setting array to itself after saving and pushing a new value #12672 #12656 + * fix(types): update replaceWith pipeline stage #12715 [coyotte508](https://github.com/coyotte508) + * fix(types): remove incorrect modelName type definition #12682 #12669 [lpizzinidev](https://github.com/lpizzinidev) + * fix(schema): fix setupTimestamps for browser.umd #12683 [raphael-papazikas](https://github.com/raphael-papazikas) + * docs: correct justOne description #12686 #12599 [tianguangcn](https://github.com/tianguangcn) + * docs: make links more consistent #12690 #12645 [hasezoey](https://github.com/hasezoey) + * docs(document): explain that $isNew is false in post('save') hooks #12685 #11990 + * docs: fixed line causing a "used before defined" linting error #12707 [sgpinkus](https://github.com/sgpinkus) + 6.7.2 / 2022-11-07 ================== * fix(discriminator): skip copying base schema plugins if `applyPlugins == false` #12613 #12604 [lpizzinidev](https://github.com/lpizzinidev) diff --git a/package.json b/package.json index 48899d57b59..ea400a3989e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "6.7.2", + "version": "6.7.3", "author": "Guillermo Rauch ", "keywords": [ "mongodb", From 0a0630b71bbe1c3eda1b802ef0aa902adf754aed Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Wed, 23 Nov 2022 09:18:15 +0100 Subject: [PATCH 06/24] fix: setting global strictQuery after Schema creation wasn't working fix #12703 --- lib/cast.js | 16 ++++++++++++++-- test/query.test.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/cast.js b/lib/cast.js index 1cb88406de4..dc04500f1a3 100644 --- a/lib/cast.js +++ b/lib/cast.js @@ -264,7 +264,7 @@ module.exports = function cast(schema, obj, options, context) { } const strict = 'strict' in options ? options.strict : schema.options.strict; - const strictQuery = getStrictQuery(options, schema._userProvidedOptions, schema.options); + const strictQuery = getStrictQuery(options, schema._userProvidedOptions, schema.options, context); if (options.upsert && strict) { if (strict === 'throw') { throw new StrictModeError(path); @@ -374,7 +374,7 @@ function _cast(val, numbertype, context) { } } -function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions) { +function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions, context) { if ('strictQuery' in queryOptions) { return queryOptions.strictQuery; } @@ -387,5 +387,17 @@ function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions) if ('strict' in schemaUserProvidedOptions) { return schemaUserProvidedOptions.strict; } + if ( + context.mongooseCollection && + context.mongooseCollection.conn && + context.mongooseCollection.conn.base) { + const mongooseOptions = context.mongooseCollection.conn.base.options; + if ('strictQuery' in mongooseOptions) { + return mongooseOptions.strictQuery; + } + if ('strict' in mongooseOptions) { + return mongooseOptions.strict; + } + } return schemaOptions.strictQuery; } diff --git a/test/query.test.js b/test/query.test.js index b56a5e6c3de..a5309ebba40 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -4263,4 +4263,47 @@ describe('Query', function() { ); }, { message: 'Can\'t modify discriminator key "animals.kind" on discriminator model' }); }); + + it('global strictQuery should work if applied after schema creation (gh-12703)', async() => { + const m = new mongoose.Mongoose(); + + await m.connect(start.uri); + + const schema = new mongoose.Schema({ title: String }); + + const Test = m.model('test', schema); + + m.set('strictQuery', false); + + await Test.create({ + title: 'chimichanga' + }); + await Test.create({ + title: 'burrito bowl' + }); + await Test.create({ + title: 'taco supreme' + }); + + const cond = { + $or: [ + { + title: { + $regex: 'urri', + $options: 'i' + } + }, + { + name: { + $regex: 'urri', + $options: 'i' + } + } + ] + }; + + const found = await Test.find(cond); + assert.strictEqual(found.length, 1); + assert.strictEqual(found[0].title, 'burrito bowl'); + }); }); From f75cf27826a6e96f2a9c4f1fb6e8642c968a4d92 Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Wed, 23 Nov 2022 09:33:36 +0100 Subject: [PATCH 07/24] added options object check --- lib/cast.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cast.js b/lib/cast.js index dc04500f1a3..019556a7e5d 100644 --- a/lib/cast.js +++ b/lib/cast.js @@ -390,7 +390,8 @@ function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions, if ( context.mongooseCollection && context.mongooseCollection.conn && - context.mongooseCollection.conn.base) { + context.mongooseCollection.conn.base && + context.mongooseCollection.conn.base.options) { const mongooseOptions = context.mongooseCollection.conn.base.options; if ('strictQuery' in mongooseOptions) { return mongooseOptions.strictQuery; From 22074a3f509aa5623e5c5bdde87085265d2d9915 Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Wed, 23 Nov 2022 09:36:01 +0100 Subject: [PATCH 08/24] fix lint --- lib/cast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cast.js b/lib/cast.js index 019556a7e5d..b65c1e3737b 100644 --- a/lib/cast.js +++ b/lib/cast.js @@ -390,7 +390,7 @@ function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions, if ( context.mongooseCollection && context.mongooseCollection.conn && - context.mongooseCollection.conn.base && + context.mongooseCollection.conn.base && context.mongooseCollection.conn.base.options) { const mongooseOptions = context.mongooseCollection.conn.base.options; if ('strictQuery' in mongooseOptions) { From c56800a4e94e5b3af54a2b1ab57bf6a8c629c45b Mon Sep 17 00:00:00 2001 From: ztk Date: Wed, 23 Nov 2022 21:33:17 +0800 Subject: [PATCH 09/24] make `modifiedPaths` safer. --- lib/helpers/common.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/helpers/common.js b/lib/helpers/common.js index c1433ce4748..13b2301533e 100644 --- a/lib/helpers/common.js +++ b/lib/helpers/common.js @@ -67,7 +67,19 @@ function flatten(update, path, options, schema) { * ignore */ -function modifiedPaths(update, path, result) { +function modifiedPaths(update, path, result, recursion=null) { + if(recursion == null){ + recursion = { + count: 0, + raw: {update, path} + }; + } + + recursion.count++; + if(recursion.count >= 1024){ + throw new Error(`Mongoose: bad update value, ${recursion.raw.update}, ${recursion.raw.path}`); + } + const keys = Object.keys(update || {}); const numKeys = keys.length; result = result || {}; @@ -83,7 +95,7 @@ function modifiedPaths(update, path, result) { val = val.toObject({ transform: false, virtuals: false }); } if (shouldFlatten(val)) { - modifiedPaths(val, path + key, result); + modifiedPaths(val, path + key, result, recursion); } } From 0ee889391bb82fe117d1209c75c38e444e068ddf Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 23 Nov 2022 16:08:21 -0500 Subject: [PATCH 10/24] fix(types): correctly infer ReadonlyArray types in schema definitions Fix #12611 --- test/types/schema.test.ts | 19 ++++++++++++++ types/inferschematype.d.ts | 53 +++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 7bd00a8d4b8..2a9f66b4d63 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -938,3 +938,22 @@ function gh12590() { }); } + +function gh12611() { + const reusableFields = { + description: { type: String, required: true }, + skills: { type: [Schema.Types.ObjectId], ref: "Skill", default: [] } + } as const; + + const firstSchema = new Schema({ + ...reusableFields, + anotherField: String + }); + + type Props = InferSchemaType; + expectType<{ + description: string; + skills: Types.ObjectId[]; + anotherField?: string; + }>({} as Props); +} diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index e8c345fc707..c4dc62f7502 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -161,29 +161,30 @@ type PathEnumOrString['enum']> = T extends ( type ResolvePathType = {}, TypeKey extends TypeKeyBaseType = DefaultTypeKey> = PathValueType extends Schema ? InferSchemaType : PathValueType extends (infer Item)[] ? IfEquals> : ObtainDocumentPathType[]> : - PathValueType extends StringSchemaDefinition ? PathEnumOrString : - IfEquals extends true ? PathEnumOrString : - IfEquals extends true ? PathEnumOrString : - PathValueType extends NumberSchemaDefinition ? Options['enum'] extends ReadonlyArray ? Options['enum'][number] : number : - IfEquals extends true ? number : - PathValueType extends DateSchemaDefinition ? Date : - IfEquals extends true ? Date : - PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : - PathValueType extends BooleanSchemaDefinition ? boolean : - IfEquals extends true ? boolean : - PathValueType extends ObjectIdSchemaDefinition ? Types.ObjectId : - IfEquals extends true ? Types.ObjectId : - IfEquals extends true ? Types.ObjectId : - PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 : - IfEquals extends true ? Types.Decimal128 : - IfEquals extends true ? Types.Decimal128 : - PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : - IfEquals extends true ? Buffer : - PathValueType extends MapConstructor ? Map> : - PathValueType extends ArrayConstructor ? any[] : - PathValueType extends typeof Schema.Types.Mixed ? any: - IfEquals extends true ? any: - IfEquals extends true ? any: - PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - PathValueType extends Record ? ObtainDocumentType : - unknown; + PathValueType extends ReadonlyArray ? IfEquals> : ObtainDocumentPathType[]> : + PathValueType extends StringSchemaDefinition ? PathEnumOrString : + IfEquals extends true ? PathEnumOrString : + IfEquals extends true ? PathEnumOrString : + PathValueType extends NumberSchemaDefinition ? Options['enum'] extends ReadonlyArray ? Options['enum'][number] : number : + IfEquals extends true ? number : + PathValueType extends DateSchemaDefinition ? Date : + IfEquals extends true ? Date : + PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : + PathValueType extends BooleanSchemaDefinition ? boolean : + IfEquals extends true ? boolean : + PathValueType extends ObjectIdSchemaDefinition ? Types.ObjectId : + IfEquals extends true ? Types.ObjectId : + IfEquals extends true ? Types.ObjectId : + PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 : + IfEquals extends true ? Types.Decimal128 : + IfEquals extends true ? Types.Decimal128 : + PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : + IfEquals extends true ? Buffer : + PathValueType extends MapConstructor ? Map> : + PathValueType extends ArrayConstructor ? any[] : + PathValueType extends typeof Schema.Types.Mixed ? any: + IfEquals extends true ? any: + IfEquals extends true ? any: + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + PathValueType extends Record ? ObtainDocumentType : + unknown; From 12706ebdd0ea039103636ef9ea4d25f7a0282f00 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 23 Nov 2022 16:14:05 -0500 Subject: [PATCH 11/24] style: fix lint --- test/types/schema.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 2a9f66b4d63..a531247e183 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -942,14 +942,14 @@ function gh12590() { function gh12611() { const reusableFields = { description: { type: String, required: true }, - skills: { type: [Schema.Types.ObjectId], ref: "Skill", default: [] } + skills: { type: [Schema.Types.ObjectId], ref: 'Skill', default: [] } } as const; - + const firstSchema = new Schema({ ...reusableFields, anotherField: String }); - + type Props = InferSchemaType; expectType<{ description: string; From 7974f004fae549ac11bbc247ac371fe6be4aa137 Mon Sep 17 00:00:00 2001 From: zzztttkkk Date: Thu, 24 Nov 2022 12:01:57 +0800 Subject: [PATCH 12/24] =?UTF-8?q?[=F0=9F=A7=AAtest]=20(test/helpers/common?= =?UTF-8?q?.test.js):=20-?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/helpers/common.js | 33 ++++++++++++++---------- test/helpers/common.test.js | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 test/helpers/common.test.js diff --git a/lib/helpers/common.js b/lib/helpers/common.js index 13b2301533e..08d9a1046e2 100644 --- a/lib/helpers/common.js +++ b/lib/helpers/common.js @@ -7,6 +7,8 @@ const Binary = require('../driver').get().Binary; const isBsonType = require('./isBsonType'); const isMongooseObject = require('./isMongooseObject'); +const MongooseError = require('../error'); +const util = require('util'); exports.flatten = flatten; exports.modifiedPaths = modifiedPaths; @@ -67,18 +69,21 @@ function flatten(update, path, options, schema) { * ignore */ -function modifiedPaths(update, path, result, recursion=null) { - if(recursion == null){ +function modifiedPaths(update, path, result, recursion = null) { + if (recursion == null) { recursion = { - count: 0, - raw: {update, path} + raw: { update, path }, + trace: new WeakSet() }; } - recursion.count++; - if(recursion.count >= 1024){ - throw new Error(`Mongoose: bad update value, ${recursion.raw.update}, ${recursion.raw.path}`); + if (recursion.trace.has(update)) { + throw new MongooseError(`a circular reference in the update value, updateValue: +${util.inspect(recursion.raw.update, { showHidden: false, depth: 1 })} +updatePath: ${recursion.raw.path} +-------`); } + recursion.trace.add(update); const keys = Object.keys(update || {}); const numKeys = keys.length; @@ -108,11 +113,11 @@ function modifiedPaths(update, path, result, recursion=null) { function shouldFlatten(val) { return val && - typeof val === 'object' && - !(val instanceof Date) && - !isBsonType(val, 'ObjectID') && - (!Array.isArray(val) || val.length !== 0) && - !(val instanceof Buffer) && - !isBsonType(val, 'Decimal128') && - !(val instanceof Binary); + typeof val === 'object' && + !(val instanceof Date) && + !isBsonType(val, 'ObjectID') && + (!Array.isArray(val) || val.length !== 0) && + !(val instanceof Buffer) && + !isBsonType(val, 'Decimal128') && + !(val instanceof Binary); } diff --git a/test/helpers/common.test.js b/test/helpers/common.test.js new file mode 100644 index 00000000000..fdbd1fa5bbd --- /dev/null +++ b/test/helpers/common.test.js @@ -0,0 +1,51 @@ +'use strict'; + +const start = require('../common'); + +const modifiedPaths = require('../../lib/helpers/common').modifiedPaths; +const mongoose = start.mongoose; +const { Schema } = mongoose; + + +describe('modifiedPaths, bad update value which has circular reference field', () => { + + it('values with obvious error', function() { + const objA = {}; + objA.a = objA; + + try { + modifiedPaths(objA, 'path', null); + } catch (e) { + console.log(e); + } + }); + + it('original error i made', async function() { + await mongoose.connect(start.uri); + + const test1Schema = new Schema({ + v: Number, + n: String + }); + + const Test1Model = mongoose.model('Test1', test1Schema); + + const test2Schema = new Schema({ + v: Number + }); + + const Test2Model = mongoose.model('Test2', test2Schema); + + for (let i = 0; i < 5; i++) { + const doc = new Test2Model({ v: i }); + await doc.save(); + } + + try { + // miss an `await` before `Test2Model.countDocuments()` + await Test1Model.updateOne({ n: 'x' }, { v: Test2Model.countDocuments() }, { upsert: true }); + } catch (e) { + console.log(e); + } + }); +}); From aad988d634ad885bb06db8180e45312333171b03 Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Thu, 24 Nov 2022 09:51:02 +0100 Subject: [PATCH 13/24] fix: code cleanup in getStrictQuery function --- lib/cast.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/cast.js b/lib/cast.js index b65c1e3737b..ea0bccdca3f 100644 --- a/lib/cast.js +++ b/lib/cast.js @@ -387,12 +387,11 @@ function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions, if ('strict' in schemaUserProvidedOptions) { return schemaUserProvidedOptions.strict; } - if ( - context.mongooseCollection && + const mongooseOptions = context.mongooseCollection && context.mongooseCollection.conn && context.mongooseCollection.conn.base && - context.mongooseCollection.conn.base.options) { - const mongooseOptions = context.mongooseCollection.conn.base.options; + context.mongooseCollection.conn.base.options; + if (mongooseOptions) { if ('strictQuery' in mongooseOptions) { return mongooseOptions.strictQuery; } From 49ee45cfb77a1f3856f8838fdc96b72eabdd249f Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Sat, 26 Nov 2022 17:38:49 +0100 Subject: [PATCH 14/24] Ignore tgz files in .npmignore --- .npmignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index 70115377db2..efbbf2be524 100644 --- a/.npmignore +++ b/.npmignore @@ -40,4 +40,6 @@ renovate.json webpack.config.js webpack.base.config.js -.nyc-output \ No newline at end of file +.nyc-output + +*.tgz From 6b6b7dbecf1947397c7790039912f203af4693db Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sat, 26 Nov 2022 14:38:38 -0500 Subject: [PATCH 15/24] fix(types): infer virtuals in query results Fix #12702 Re: #11908 --- test/types/virtuals.test.ts | 5 ++++- types/index.d.ts | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/test/types/virtuals.test.ts b/test/types/virtuals.test.ts index a5b07f54419..0e12483d79e 100644 --- a/test/types/virtuals.test.ts +++ b/test/types/virtuals.test.ts @@ -87,7 +87,7 @@ function gh11543() { expectType(personSchema.virtuals); } -function autoTypedVirtuals() { +async function autoTypedVirtuals() { type AutoTypedSchemaType = InferSchemaType; type VirtualsType = { domain: string }; type InferredDocType = FlatRecord>; @@ -119,4 +119,7 @@ function autoTypedVirtuals() { expectType(doc.domain); expectType>({} as InferredDocType); + + const doc2 = await TestModel.findOne().orFail(); + expectType(doc2.domain); } diff --git a/types/index.d.ts b/types/index.d.ts index b1ce3eabffb..7b9b70714b3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -78,7 +78,13 @@ declare module 'mongoose' { schema?: TSchema, collection?: string, options?: CompileModelOptions - ): Model, ObtainSchemaGeneric, ObtainSchemaGeneric, {}, TSchema> & ObtainSchemaGeneric; + ): Model< + InferSchemaType, + ObtainSchemaGeneric, + ObtainSchemaGeneric, + ObtainSchemaGeneric, + TSchema + > & ObtainSchemaGeneric; export function model(name: string, schema?: Schema | Schema, collection?: string, options?: CompileModelOptions): Model; From f0967be47bdcf28cc3052d0734ceebc00e11835e Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Sun, 27 Nov 2022 11:07:38 +0100 Subject: [PATCH 16/24] fix: increased timeout for flaky test --- test/errors.validation.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/errors.validation.test.js b/test/errors.validation.test.js index b881d4bf80a..d4081997372 100644 --- a/test/errors.validation.test.js +++ b/test/errors.validation.test.js @@ -234,6 +234,8 @@ describe('ValidationError', function() { describe('when user code defines a r/o Error#toJSON', function() { it('should not fail', function(done) { + this.timeout(10000); + const err = []; const child = require('child_process') .fork('./test/isolated/project-has-error.toJSON.js', ['--no-warnings'], { silent: true }); From 88d584db40c87971753eee243a5cda1d2a6468cc Mon Sep 17 00:00:00 2001 From: zzztttkkk Date: Mon, 28 Nov 2022 10:32:41 +0800 Subject: [PATCH 17/24] =?UTF-8?q?[=F0=9F=90=9Bfix]=20(lib/help/common.js):?= =?UTF-8?q?=20update=20value=20can=20be=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/helpers/common.js | 5 +++-- test/helpers/common.test.js | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/helpers/common.js b/lib/helpers/common.js index 08d9a1046e2..c12db0aacda 100644 --- a/lib/helpers/common.js +++ b/lib/helpers/common.js @@ -70,6 +70,8 @@ function flatten(update, path, options, schema) { */ function modifiedPaths(update, path, result, recursion = null) { + if (update == null) return result; + if (recursion == null) { recursion = { raw: { update, path }, @@ -80,8 +82,7 @@ function modifiedPaths(update, path, result, recursion = null) { if (recursion.trace.has(update)) { throw new MongooseError(`a circular reference in the update value, updateValue: ${util.inspect(recursion.raw.update, { showHidden: false, depth: 1 })} -updatePath: ${recursion.raw.path} --------`); +updatePath: '${recursion.raw.path}'`); } recursion.trace.add(update); diff --git a/test/helpers/common.test.js b/test/helpers/common.test.js index fdbd1fa5bbd..bd53b5ac213 100644 --- a/test/helpers/common.test.js +++ b/test/helpers/common.test.js @@ -9,6 +9,10 @@ const { Schema } = mongoose; describe('modifiedPaths, bad update value which has circular reference field', () => { + it('update value can be null', function() { + modifiedPaths(null, 'path', null); + }); + it('values with obvious error', function() { const objA = {}; objA.a = objA; From 62ad08d98aae94b72aa4d88c00424c07d5e48296 Mon Sep 17 00:00:00 2001 From: zzztttkkk Date: Mon, 28 Nov 2022 10:41:26 +0800 Subject: [PATCH 18/24] =?UTF-8?q?[=F0=9F=90=9Bfix]=20(lib/helpers/common.j?= =?UTF-8?q?s):=20-?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/helpers/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers/common.js b/lib/helpers/common.js index c12db0aacda..398dc4c3229 100644 --- a/lib/helpers/common.js +++ b/lib/helpers/common.js @@ -70,7 +70,7 @@ function flatten(update, path, options, schema) { */ function modifiedPaths(update, path, result, recursion = null) { - if (update == null) return result; + if (update == null) return result || {}; if (recursion == null) { recursion = { From 23d44e4e8eada462d23c73cc103a7e23fda85688 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 28 Nov 2022 13:18:59 -0500 Subject: [PATCH 19/24] fix: quick fix to avoid checking non-objects for modified paths --- lib/helpers/common.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/helpers/common.js b/lib/helpers/common.js index 398dc4c3229..fb3dbdd098a 100644 --- a/lib/helpers/common.js +++ b/lib/helpers/common.js @@ -70,7 +70,9 @@ function flatten(update, path, options, schema) { */ function modifiedPaths(update, path, result, recursion = null) { - if (update == null) return result || {}; + if (update == null || typeof update !== 'object') { + return; + } if (recursion == null) { recursion = { From f899973c619c4777bd821dbaf1b305901d8e15dd Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 28 Nov 2022 13:27:45 -0500 Subject: [PATCH 20/24] test: make tests for #12719 more durable instead of relying on console log --- test/helpers/common.test.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/test/helpers/common.test.js b/test/helpers/common.test.js index bd53b5ac213..0e2cc147b44 100644 --- a/test/helpers/common.test.js +++ b/test/helpers/common.test.js @@ -17,11 +17,7 @@ describe('modifiedPaths, bad update value which has circular reference field', ( const objA = {}; objA.a = objA; - try { - modifiedPaths(objA, 'path', null); - } catch (e) { - console.log(e); - } + assert.throws(() => modifiedPaths(objA, 'path', null), /circular reference/); }); it('original error i made', async function() { @@ -44,12 +40,7 @@ describe('modifiedPaths, bad update value which has circular reference field', ( const doc = new Test2Model({ v: i }); await doc.save(); } - - try { - // miss an `await` before `Test2Model.countDocuments()` - await Test1Model.updateOne({ n: 'x' }, { v: Test2Model.countDocuments() }, { upsert: true }); - } catch (e) { - console.log(e); - } + + assert.rejects(() => Test1Model.updateOne({ n: 'x' }, { v: Test2Model.countDocuments() }, { upsert: true }), /circular reference/); }); }); From b393f87847644ae763068489a5e37155cbbeb213 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 28 Nov 2022 13:32:35 -0500 Subject: [PATCH 21/24] style: fix lint --- test/helpers/common.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helpers/common.test.js b/test/helpers/common.test.js index 0e2cc147b44..1e98764a347 100644 --- a/test/helpers/common.test.js +++ b/test/helpers/common.test.js @@ -1,5 +1,6 @@ 'use strict'; +const assert = require('assert'); const start = require('../common'); const modifiedPaths = require('../../lib/helpers/common').modifiedPaths; From f60b59a9be101400eb09ee5b1bd182cdeb77f336 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 28 Nov 2022 13:35:56 -0500 Subject: [PATCH 22/24] test: remove unnecessary test --- test/helpers/common.test.js | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/test/helpers/common.test.js b/test/helpers/common.test.js index 1e98764a347..4d10afb8419 100644 --- a/test/helpers/common.test.js +++ b/test/helpers/common.test.js @@ -9,39 +9,14 @@ const { Schema } = mongoose; describe('modifiedPaths, bad update value which has circular reference field', () => { - it('update value can be null', function() { modifiedPaths(null, 'path', null); }); - it('values with obvious error', function() { + it('values with obvious error on circular reference', function() { const objA = {}; objA.a = objA; assert.throws(() => modifiedPaths(objA, 'path', null), /circular reference/); }); - - it('original error i made', async function() { - await mongoose.connect(start.uri); - - const test1Schema = new Schema({ - v: Number, - n: String - }); - - const Test1Model = mongoose.model('Test1', test1Schema); - - const test2Schema = new Schema({ - v: Number - }); - - const Test2Model = mongoose.model('Test2', test2Schema); - - for (let i = 0; i < 5; i++) { - const doc = new Test2Model({ v: i }); - await doc.save(); - } - - assert.rejects(() => Test1Model.updateOne({ n: 'x' }, { v: Test2Model.countDocuments() }, { upsert: true }), /circular reference/); - }); }); From 457f821cf6216de3d3e16c223c2a6f2054c3b4e6 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 28 Nov 2022 13:41:48 -0500 Subject: [PATCH 23/24] style: fix lint --- test/helpers/common.test.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/helpers/common.test.js b/test/helpers/common.test.js index 4d10afb8419..93dd982ae6b 100644 --- a/test/helpers/common.test.js +++ b/test/helpers/common.test.js @@ -1,12 +1,7 @@ 'use strict'; const assert = require('assert'); -const start = require('../common'); - const modifiedPaths = require('../../lib/helpers/common').modifiedPaths; -const mongoose = start.mongoose; -const { Schema } = mongoose; - describe('modifiedPaths, bad update value which has circular reference field', () => { it('update value can be null', function() { From 66474c9bceb5e1749b21fae8390cb8ae817f0567 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 28 Nov 2022 13:48:11 -0500 Subject: [PATCH 24/24] chore: release 6.7.4 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17432a1ed80..ced766bcd51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +6.7.4 / 2022-11-28 +================== + * fix: allow setting global strictQuery after Schema creation #12717 #12703 [lpizzinidev](https://github.com/lpizzinidev) + * fix(cursor): make eachAsync() avoid modifying batch when mixing parallel and batchSize #12716 + * fix(types): infer virtuals in query results #12727 #12702 #12684 + * fix(types): correctly infer ReadonlyArray types in schema definitions #12720 + * fix(types): avoid typeof Query with generics for TypeScript 4.6 support #12712 #12688 + * chore: avoid bundling .tgz files when publishing #12725 [hasezoey](https://github.com/hasezoey) + 6.7.3 / 2022-11-22 ================== * fix(document): handle setting array to itself after saving and pushing a new value #12672 #12656 diff --git a/package.json b/package.json index ea400a3989e..14654ec555c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "6.7.3", + "version": "6.7.4", "author": "Guillermo Rauch ", "keywords": [ "mongodb",