Skip to content

Commit b630afb

Browse files
committed
docs(migrating_to_8): add missing issues to migration guide
1 parent eefe935 commit b630afb

File tree

2 files changed

+173
-14
lines changed

2 files changed

+173
-14
lines changed

docs/migrating_to_8.md

Lines changed: 168 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@ If you're still on Mongoose 6.x or earlier, please read the [Mongoose 6.x to 7.x
1313

1414
* [Removed `rawResult` option for `findOneAndUpdate()`](#removed-rawresult-option-for-findoneandupdate)
1515
* [`Document.prototype.deleteOne()` now returns a query](#document-prototype-deleteone-now-returns-a-query)
16-
* [Changed behavior for `findOneAndUpdate()` with `orFail()` and upsert](#changed-behavior-for-findoneandupdate-with-orfail-and-upsert)
1716
* [MongoDB Node Driver 6.0](#mongodb-node-driver-6)
1817
* [Removed `findOneAndRemove()`](#removed-findoneandremove)
18+
* [Removed `count()`](#removed-count)
1919
* [Removed id Setter](#removed-id-setter)
20+
* [`null` is valid for non-required string enums](#null-is-valid-for-non-required-string-enums)
21+
* [Apply minimize when `save()` updates an existing document](#apply-minimize-when-save-updates-an-existing-document)
22+
* [Apply base schema paths before discriminator paths](#apply-base-schema-paths-before-discriminator-paths)
23+
* [Changed behavior for `findOneAndUpdate()` with `orFail()` and upsert](#changed-behavior-for-findoneandupdate-with-orfail-and-upsert)
24+
* [`create()` waits until all saves are done before throwing any error](#create-waits-until-all-saves-are-done-before-throwing-any-error)
25+
* [`Model.validate()` returns copy of object](#model-validate-returns-copy-of-object)
2026
* [Allow `null` For Optional Fields in TypeScript](#allow-null-for-optional-fields-in-typescript)
27+
* [Infer `distinct()` return types from schema](#infer-distinct-return-types-from-schema)
2128

2229
<h2 id="removed-rawresult-option-for-findoneandupdate"><a href="#removed-rawresult-option-for-findoneandupdate">Removed <code>rawResult</code> option for <code>findOneAndUpdate()</code></a></h2>
2330

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

57-
<h2 id="changed-behavior-for-findoneandupdate-with-orfail-and-upsert"><a href="#changed-behavior-for-findoneandupdate-with-orfail-and-upsert">Changed behavior for <code>findOneAndUpdate()</code> with <code>orFail()</code> and upsert</a></h2>
58-
59-
In Mongoose 7, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` would throw a `DocumentNotFoundError` if a new document was upserted.
60-
In other words, `findOneAndUpdate().orFail()` always threw an error if no document was found, even if a new document was upserted.
61-
62-
In Mongoose 8, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` always succeeds.
63-
`findOneAndUpdate().orFail()` now throws a `DocumentNotFoundError` if there's no document returned, rather than if no document was found.
64-
6564
<h2 id="mongodb-node-driver-6"><a href="#mongodb-node-driver-6">MongoDB Node Driver 6</a></h2>
6665

6766
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
7574
Mongoose 8 no longer supports `findOneAndRemove()`.
7675
Use `findOneAndDelete()` instead.
7776

77+
<h2 id="removed-count"><a href="#removed-count">Removed <code>count()</code></a></h2>
78+
79+
`Model.count()` and `Query.prototype.count()` were removed in Mongoose 8. Use `Model.countDocuments()` and `Query.prototype.countDocuments()` instead.
80+
7881
<h2 id="removed-id-setter"><a href="#removed-id-setter">Removed id Setter</a></h2>
7982

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

86+
<h2 id="null-is-valid-for-non-required-string-enums"><a href="#null-is-valid-for-non-required-string-enums"><code>null</code> is valid for non-required string enums</a></h2>
87+
88+
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`.
89+
In Mongoose 8, it is valid to set a string path to `null` if `required` is not set, even with `enum`.
90+
91+
```javascript
92+
const schema = new Schema({
93+
status: {
94+
type: String,
95+
enum: ['on', 'off']
96+
}
97+
});
98+
const Test = mongoose.model('Test', schema);
99+
100+
// Works fine in Mongoose 8
101+
// Throws a `ValidationError` in Mongoose 7
102+
await Test.create({ status: null });
103+
```
104+
105+
<h2 id="apply-minimize-when-save-updates-an-existing-document"><a href="#apply-minimize-when-save-updates-an-existing-document">Apply minimize when <code>save()</code> updates an existing document</a></h2>
106+
107+
In Mongoose 7, Mongoose would only apply minimize when saving a new document, not when updating an existing document.
108+
109+
```javascript
110+
const schema = new Schema({
111+
nested: {
112+
field1: Number
113+
}
114+
});
115+
const Test = mongoose.model('Test', schema);
116+
117+
// Both Mongoose 7 and Mongoose 8 strip out empty objects when saving
118+
// a new document in MongoDB by default
119+
const { _id } = await Test.create({ nested: {} });
120+
let rawDoc = await Test.findById(_id).lean();
121+
rawDoc.nested; // undefined
122+
123+
// Mongoose 8 will also strip out empty objects when saving an
124+
// existing document in MongoDB
125+
const doc = await Test.findById(_id);
126+
doc.nested = {};
127+
doc.markModified('nested');
128+
await doc.save();
129+
130+
let rawDoc = await Test.findById(_id).lean();
131+
rawDoc.nested; // undefined in Mongoose 8, {} in Mongoose 7
132+
```
133+
134+
<h2 id="apply-base-schema-paths-before-discriminator-paths"><a href="#apply-base-schema-paths-before-discriminator-paths">Apply base schema paths before discriminator paths</a></h2>
135+
136+
This means that, in Mongoose 8, getters and setters on discriminator paths run _after_ getters and setters on base paths.
137+
In Mongoose 7, getters and setters on discriminator paths ran _before_ getters and setters on base paths.
138+
139+
```javascript
140+
141+
const schema = new Schema({
142+
name: {
143+
type: String,
144+
get(v) {
145+
console.log('Base schema getter');
146+
return v;
147+
}
148+
}
149+
});
150+
151+
const Test = mongoose.model('Test', schema);
152+
const D = Test.discriminator('D', new Schema({
153+
otherProp: {
154+
type: String,
155+
get(v) {
156+
console.log('Discriminator schema getter');
157+
return v;
158+
}
159+
}
160+
}));
161+
162+
const doc = new D({ name: 'test', otherProp: 'test' });
163+
// In Mongoose 8, prints "Base schema getter" followed by "Discriminator schema getter"
164+
// In Mongoose 7, prints "Discriminator schema getter" followed by "Base schema getter"
165+
console.log(doc.toObject({ getters: true }));
166+
```
167+
168+
<h2 id="changed-behavior-for-findoneandupdate-with-orfail-and-upsert"><a href="#changed-behavior-for-findoneandupdate-with-orfail-and-upsert">Changed behavior for <code>findOneAndUpdate()</code> with <code>orFail()</code> and upsert</a></h2>
169+
170+
In Mongoose 7, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` would throw a `DocumentNotFoundError` if a new document was upserted.
171+
In other words, `findOneAndUpdate().orFail()` always threw an error if no document was found, even if a new document was upserted.
172+
173+
In Mongoose 8, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` always succeeds.
174+
`findOneAndUpdate().orFail()` now throws a `DocumentNotFoundError` if there's no document returned, rather than if no document was found.
175+
176+
<h2 id="create-waits-until-all-saves-are-done-before-throwing-any-error"><a href="#create-waits-until-all-saves-are-done-before-throwing-any-error"><code>create()</code> waits until all saves are done before throwing any error</a></h2>
177+
178+
In Mongoose 7, `create()` would immediately throw if any `save()` threw an error by default.
179+
Mongoose 8 instead waits for all `save()` calls to finish before throwing the first error that occurred.
180+
So `create()` will throw the same error in both Mongoose 7 and Mongoose 8, Mongoose 8 just may take longer to throw the error.
181+
182+
```javascript
183+
const schema = new Schema({
184+
name: {
185+
type: String,
186+
enum: ['Badger', 'Mushroom']
187+
}
188+
});
189+
schema.pre('save', async function() {
190+
await new Promise(resolve => setTimeout(resolve, 1000));
191+
});
192+
const Test = mongoose.model('Test', schema);
193+
194+
const err = await Test.create([
195+
{ name: 'Badger' },
196+
{ name: 'Mushroom' },
197+
{ name: 'Cow' }
198+
]).then(() => null, err => err);
199+
err; // ValidationError
200+
201+
// In Mongoose 7, there would be 0 documents, because `Test.create()`
202+
// would throw before 'Badger' and 'Mushroom' are inserted
203+
// In Mongoose 8, there will be 2 documents. `Test.create()` waits until
204+
// 'Badger' and 'Mushroom' are inserted before throwing.
205+
await Test.countDocuments();
206+
```
207+
208+
<h2 id="model-validate-returns-copy-of-object"><a href="#model-validate-returns-copy-of-object"><code>Model.validate()</code> returns copy of object</a></h2>
209+
210+
In Mongoose 7, `Model.validate()` would potentially modify the passed in object.
211+
Mongoose 8 instead copies the passed in object first.
212+
213+
```javascript
214+
const schema = new Schema({ answer: Number });
215+
const Test = mongoose.model('Test', schema);
216+
217+
const obj = { answer: '42' };
218+
const res = Test.validate(obj);
219+
220+
typeof obj.answer; // 'string' in Mongoose 8, 'number' in Mongoose 7
221+
typeof res.answer; // 'number' in both Mongoose 7 and Mongoose 8
222+
```
223+
83224
<h2 id="allow-null-for-optional-fields-in-typescript"><a href="#allow-null-for-optional-fields-in-typescript">Allow <code>null</code> For Optional Fields in TypeScript</a></h2>
84225

85226
In Mongoose 8, automatically inferred schema types in TypeScript allow `null` for optional fields.
@@ -95,3 +236,21 @@ const doc = new TestModel();
95236
// In Mongoose 7, this type is `string | undefined`
96237
doc.name;
97238
```
239+
240+
<h2 id="infer-distinct-return-types-from-schema"><a href="#infer-distinct-return-types-from-schema">Infer <code>distinct()</code> return types from schema</a></h2>
241+
242+
```ts
243+
interface User {
244+
name: string;
245+
email: string;
246+
avatar?: string;
247+
}
248+
const schema = new Schema<User>({
249+
name: { type: String, required: true },
250+
email: { type: String, required: true },
251+
avatar: String
252+
});
253+
254+
// Works in Mongoose 8. Compile error in Mongoose 7.
255+
const names: string[] = await MyModel.distinct('name');
256+
```

types/inferschematype.d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ declare module 'mongoose' {
3232
EnforcedDocType :
3333
{
3434
[
35-
K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
36-
OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)
35+
K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
36+
OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)
3737
]: IsPathRequired<DocDefinition[K], TSchemaOptions['typeKey']> extends true ?
3838
ObtainDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']> :
3939
ObtainDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']> | null;
40-
};
40+
};
4141

42-
/**
42+
/**
4343
* @summary Obtains document schema type from Schema instance.
4444
* @param {Schema} TSchema `typeof` a schema instance.
4545
* @example
@@ -48,7 +48,7 @@ declare module 'mongoose' {
4848
* // result
4949
* type UserType = {userName?: string}
5050
*/
51-
export type InferSchemaType<TSchema> = IfAny<TSchema, any, ObtainSchemaGeneric<TSchema, 'DocType'>>;
51+
export type InferSchemaType<TSchema> = IfAny<TSchema, any, ObtainSchemaGeneric<TSchema, 'DocType'>>;
5252

5353
/**
5454
* @summary Obtains schema Generic type by using generic alias.

0 commit comments

Comments
 (0)