From b630afb1ea653431769659a07d2d80b7cc22ae8b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 19 Oct 2023 18:24:41 -0400 Subject: [PATCH] docs(migrating_to_8): add missing issues to migration guide --- docs/migrating_to_8.md | 177 +++++++++++++++++++++++++++++++++++-- types/inferschematype.d.ts | 10 +-- 2 files changed, 173 insertions(+), 14 deletions(-) diff --git a/docs/migrating_to_8.md b/docs/migrating_to_8.md index d2ce01f8ee6..459a2f64ea5 100644 --- a/docs/migrating_to_8.md +++ b/docs/migrating_to_8.md @@ -13,11 +13,18 @@ If you're still on Mongoose 6.x or earlier, please read the [Mongoose 6.x to 7.x * [Removed `rawResult` option for `findOneAndUpdate()`](#removed-rawresult-option-for-findoneandupdate) * [`Document.prototype.deleteOne()` now returns a query](#document-prototype-deleteone-now-returns-a-query) -* [Changed behavior for `findOneAndUpdate()` with `orFail()` and upsert](#changed-behavior-for-findoneandupdate-with-orfail-and-upsert) * [MongoDB Node Driver 6.0](#mongodb-node-driver-6) * [Removed `findOneAndRemove()`](#removed-findoneandremove) +* [Removed `count()`](#removed-count) * [Removed id Setter](#removed-id-setter) +* [`null` is valid for non-required string enums](#null-is-valid-for-non-required-string-enums) +* [Apply minimize when `save()` updates an existing document](#apply-minimize-when-save-updates-an-existing-document) +* [Apply base schema paths before discriminator paths](#apply-base-schema-paths-before-discriminator-paths) +* [Changed behavior for `findOneAndUpdate()` with `orFail()` and upsert](#changed-behavior-for-findoneandupdate-with-orfail-and-upsert) +* [`create()` waits until all saves are done before throwing any error](#create-waits-until-all-saves-are-done-before-throwing-any-error) +* [`Model.validate()` returns copy of object](#model-validate-returns-copy-of-object) * [Allow `null` For Optional Fields in TypeScript](#allow-null-for-optional-fields-in-typescript) +* [Infer `distinct()` return types from schema](#infer-distinct-return-types-from-schema)

Removed rawResult option for findOneAndUpdate()

@@ -54,14 +61,6 @@ const q = numberOne.deleteOne(); const res = await q; ``` -

Changed behavior for findOneAndUpdate() with orFail() and upsert

- -In Mongoose 7, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` would throw a `DocumentNotFoundError` if a new document was upserted. -In other words, `findOneAndUpdate().orFail()` always threw an error if no document was found, even if a new document was upserted. - -In Mongoose 8, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` always succeeds. -`findOneAndUpdate().orFail()` now throws a `DocumentNotFoundError` if there's no document returned, rather than if no document was found. -

MongoDB Node Driver 6

Mongoose 8 uses [v6.x of the MongoDB Node driver](https://github.com/mongodb/node-mongodb-native/blob/main/HISTORY.md#600-2023-08-28). @@ -75,11 +74,153 @@ In Mongoose 7, `findOneAndRemove()` was an alias for `findOneAndDelete()` that M Mongoose 8 no longer supports `findOneAndRemove()`. Use `findOneAndDelete()` instead. +

Removed count()

+ +`Model.count()` and `Query.prototype.count()` were removed in Mongoose 8. Use `Model.countDocuments()` and `Query.prototype.countDocuments()` instead. +

Removed id Setter

In Mongoose 7.4, Mongoose introduced an `id` setter that made `doc.id = '0'.repeat(24)` equivalent to `doc._id = '0'.repeat(24)`. In Mongoose 8, that setter is now removed. +

null is valid for non-required string enums

+ +Before Mongoose 8, setting a string path with an `enum` to `null` would lead to a validation error, even if that path wasn't `required`. +In Mongoose 8, it is valid to set a string path to `null` if `required` is not set, even with `enum`. + +```javascript +const schema = new Schema({ + status: { + type: String, + enum: ['on', 'off'] + } +}); +const Test = mongoose.model('Test', schema); + +// Works fine in Mongoose 8 +// Throws a `ValidationError` in Mongoose 7 +await Test.create({ status: null }); +``` + +

Apply minimize when save() updates an existing document

+ +In Mongoose 7, Mongoose would only apply minimize when saving a new document, not when updating an existing document. + +```javascript +const schema = new Schema({ + nested: { + field1: Number + } +}); +const Test = mongoose.model('Test', schema); + +// Both Mongoose 7 and Mongoose 8 strip out empty objects when saving +// a new document in MongoDB by default +const { _id } = await Test.create({ nested: {} }); +let rawDoc = await Test.findById(_id).lean(); +rawDoc.nested; // undefined + +// Mongoose 8 will also strip out empty objects when saving an +// existing document in MongoDB +const doc = await Test.findById(_id); +doc.nested = {}; +doc.markModified('nested'); +await doc.save(); + +let rawDoc = await Test.findById(_id).lean(); +rawDoc.nested; // undefined in Mongoose 8, {} in Mongoose 7 +``` + +

Apply base schema paths before discriminator paths

+ +This means that, in Mongoose 8, getters and setters on discriminator paths run _after_ getters and setters on base paths. +In Mongoose 7, getters and setters on discriminator paths ran _before_ getters and setters on base paths. + +```javascript + +const schema = new Schema({ + name: { + type: String, + get(v) { + console.log('Base schema getter'); + return v; + } + } +}); + +const Test = mongoose.model('Test', schema); +const D = Test.discriminator('D', new Schema({ + otherProp: { + type: String, + get(v) { + console.log('Discriminator schema getter'); + return v; + } + } +})); + +const doc = new D({ name: 'test', otherProp: 'test' }); +// In Mongoose 8, prints "Base schema getter" followed by "Discriminator schema getter" +// In Mongoose 7, prints "Discriminator schema getter" followed by "Base schema getter" +console.log(doc.toObject({ getters: true })); +``` + +

Changed behavior for findOneAndUpdate() with orFail() and upsert

+ +In Mongoose 7, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` would throw a `DocumentNotFoundError` if a new document was upserted. +In other words, `findOneAndUpdate().orFail()` always threw an error if no document was found, even if a new document was upserted. + +In Mongoose 8, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` always succeeds. +`findOneAndUpdate().orFail()` now throws a `DocumentNotFoundError` if there's no document returned, rather than if no document was found. + +

create() waits until all saves are done before throwing any error

+ +In Mongoose 7, `create()` would immediately throw if any `save()` threw an error by default. +Mongoose 8 instead waits for all `save()` calls to finish before throwing the first error that occurred. +So `create()` will throw the same error in both Mongoose 7 and Mongoose 8, Mongoose 8 just may take longer to throw the error. + +```javascript +const schema = new Schema({ + name: { + type: String, + enum: ['Badger', 'Mushroom'] + } +}); +schema.pre('save', async function() { + await new Promise(resolve => setTimeout(resolve, 1000)); +}); +const Test = mongoose.model('Test', schema); + +const err = await Test.create([ + { name: 'Badger' }, + { name: 'Mushroom' }, + { name: 'Cow' } +]).then(() => null, err => err); +err; // ValidationError + +// In Mongoose 7, there would be 0 documents, because `Test.create()` +// would throw before 'Badger' and 'Mushroom' are inserted +// In Mongoose 8, there will be 2 documents. `Test.create()` waits until +// 'Badger' and 'Mushroom' are inserted before throwing. +await Test.countDocuments(); +``` + +

Model.validate() returns copy of object

+ +In Mongoose 7, `Model.validate()` would potentially modify the passed in object. +Mongoose 8 instead copies the passed in object first. + +```javascript +const schema = new Schema({ answer: Number }); +const Test = mongoose.model('Test', schema); + +const obj = { answer: '42' }; +const res = Test.validate(obj); + +typeof obj.answer; // 'string' in Mongoose 8, 'number' in Mongoose 7 +typeof res.answer; // 'number' in both Mongoose 7 and Mongoose 8 +``` +

Allow null For Optional Fields in TypeScript

In Mongoose 8, automatically inferred schema types in TypeScript allow `null` for optional fields. @@ -95,3 +236,21 @@ const doc = new TestModel(); // In Mongoose 7, this type is `string | undefined` doc.name; ``` + +

Infer distinct() return types from schema

+ +```ts +interface User { + name: string; + email: string; + avatar?: string; +} +const schema = new Schema({ + name: { type: String, required: true }, + email: { type: String, required: true }, + avatar: String +}); + +// Works in Mongoose 8. Compile error in Mongoose 7. +const names: string[] = await MyModel.distinct('name'); +``` \ No newline at end of file diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 98e5fd2ff7a..e4ea5ebe327 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -32,14 +32,14 @@ declare module 'mongoose' { EnforcedDocType : { [ - K in keyof (RequiredPaths & - OptionalPaths) + K in keyof (RequiredPaths & + OptionalPaths) ]: IsPathRequired extends true ? ObtainDocumentPathType : ObtainDocumentPathType | null; - }; + }; - /** + /** * @summary Obtains document schema type from Schema instance. * @param {Schema} TSchema `typeof` a schema instance. * @example @@ -48,7 +48,7 @@ declare module 'mongoose' { * // result * type UserType = {userName?: string} */ - export type InferSchemaType = IfAny>; + export type InferSchemaType = IfAny>; /** * @summary Obtains schema Generic type by using generic alias.