从 7.x 迁移到 8.x

¥Migrating from 7.x to 8.x

从 Mongoose 7.x 迁移到 Mongoose 8.x 时,你应该注意一些向后破坏的更改。

¥There are several backwards-breaking changes you should be aware of when migrating from Mongoose 7.x to Mongoose 8.x.

如果你仍在使用 Mongoose 6.x 或更早版本,请先阅读 Mongoose 6.x 到 7.x 迁移指南 并升级到 Mongoose 7.x,然后再升级到 Mongoose 8。

¥If you're still on Mongoose 6.x or earlier, please read the Mongoose 6.x to 7.x migration guide and upgrade to Mongoose 7.x first before upgrading to Mongoose 8.

我们还建议在升级到 Mongoose 8 之前检查 MongoDB Node.js 驱动程序 v6.0.0 的发行说明

¥We also recommend reviewing the MongoDB Node.js driver's release notes for v6.0.0 before upgrading to Mongoose 8.

删除了 findOneAndUpdate()rawResult 选项

findOneAndUpdate()findOneAndReplace()findOneAndDelete()rawResult 选项已被 includeResultMetadata 选项取代。

¥The rawResult option for findOneAndUpdate(), findOneAndReplace(), and findOneAndDelete() has been replaced by the includeResultMetadata option.

const filter = { name: 'Will Riker' };
const update = { age: 29 };

const res = await Character.findOneAndUpdate(filter, update, {
  new: true,
  upsert: true,
  // Replace `rawResult: true` with `includeResultMetadata: true`
  includeResultMetadata: true
});

Mongoose 8 中的 includeResultMetadata 的行为与 rawResult 相同。

¥includeResultMetadata in Mongoose 8 behaves identically to rawResult.

Document.prototype.deleteOne 现在返回一个查询

在 Mongoose 7 中,doc.deleteOne() 返回了一个解析为 doc 的 promise。在 Mongoose 8 中,doc.deleteOne() 返回一个查询,以便于链接,并与 doc.updateOne() 保持一致。

¥In Mongoose 7, doc.deleteOne() returned a promise that resolved to doc. In Mongoose 8, doc.deleteOne() returns a query for easier chaining, as well as consistency with doc.updateOne().

const numberOne = await Character.findOne({ name: 'Will Riker' });

// In Mongoose 7, q is a Promise that resolves to `numberOne`
// In Mongoose 8, q is a Query.
const q = numberOne.deleteOne();

// In Mongoose 7, `res === numberOne`
// In Mongoose 8, `res` is a `DeleteResult`.
const res = await q;

MongoDB Node 驱动 6

Mongoose 8 使用 MongoDB Node 驱动程序 v6.x。MongoDB Node 驱动程序 v6 中有一些影响 Mongoose 的显着变化:

¥Mongoose 8 uses v6.x of the MongoDB Node driver. There's a few noteable changes in MongoDB Node driver v6 that affect Mongoose:

  1. ObjectId 构造函数不再接受长度为 12 的字符串。在 Mongoose 7 中,new mongoose.Types.ObjectId('12charstring') 完全有效。在 Mongoose 8 中,new mongoose.Types.ObjectId('12charstring') 抛出错误。

    ¥The ObjectId constructor no longer accepts strings of length 12. In Mongoose 7, new mongoose.Types.ObjectId('12charstring') was perfectly valid. In Mongoose 8, new mongoose.Types.ObjectId('12charstring') throws an error.

  2. 已删除已弃用的 SSL 选项

    ¥Deprecated SSL options have been removed

    • sslCA -> tlsCAFile

    • sslCRL -> tlsCRLFile

    • sslCert -> tlsCertificateKeyFile

    • sslKey -> tlsCertificateKeyFile

    • sslPass -> tlsCertificateKeyFilePassword

    • sslValidate -> tlsAllowInvalidCertificates

    • tlsCertificateFile -> tlsCertificateKeyFile

删除了 findOneAndRemove()

在 Mongoose 7 中,findOneAndRemove() 是 Mongoose 支持向后兼容的 findOneAndDelete() 的别名。Mongoose 8 不再支持 findOneAndRemove()。请改用 findOneAndDelete()

¥In Mongoose 7, findOneAndRemove() was an alias for findOneAndDelete() that Mongoose supported for backwards compatibility. Mongoose 8 no longer supports findOneAndRemove(). Use findOneAndDelete() instead.

删除了 count()

Model.count()Query.prototype.count() 在 Mongoose 8 中被删除。请改用 Model.countDocuments()Query.prototype.countDocuments()

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

删除了 id 设置器

在 Mongoose 7.4 中,Mongoose 引入了 id setter,使 doc.id = '0'.repeat(24) 等同于 doc._id = '0'.repeat(24)。在 Mongoose 8 中,该 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 对于非必需的字符串枚举有效

在 Mongoose 8 之前,设置包含 enumnull 的字符串路径会导致验证错误,即使该路径不是 required。在 Mongoose 8 中,如果未设置 required,则将字符串路径设置为 null 是有效的,即使设置了 enum

¥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.

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 });

save() 更新现有文档时应用最小化

在 Mongoose 7 中,Mongoose 仅在保存新文档时应用最小化,而不是在更新现有文档时应用最小化。

¥In Mongoose 7, Mongoose would only apply minimize when saving a new document, not when updating an existing document.

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

在鉴别器路径之前应用基本模式路径

这意味着,在 Mongoose 8 中,鉴别器路径上的 getter 和 setter 在基本路径上的 getter 和 setter 之后运行。在 Mongoose 7 中,鉴别器路径上的 getter 和 setter 在基本路径上的 getter 和 setter 之前运行。

¥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.


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 }));

删除了 findOneAndUpdate()overwrite 选项

Mongoose 7 及更早版本支持 findOneAndUpdate()updateOne()update()overwrite 选项。在 Mongoose 7 之前,overwrite 会跳过将 update 参数封装到 $set 中,这意味着 findOneAndUpdate()update() 会覆盖匹配的文档。在 Mongoose 7 中,设置 overwrite 会将 findOneAndUpdate() 转换为 findOneAndReplace(),将 updateOne() 转换为 replaceOne(),以保留向后兼容性。

¥Mongoose 7 and earlier supported an overwrite option for findOneAndUpdate(), updateOne(), and update(). Before Mongoose 7, overwrite would skip wrapping the update parameter in $set, which meant that findOneAndUpdate() and update() would overwrite the matched document. In Mongoose 7, setting overwrite would convert findOneAndUpdate() to findOneAndReplace() and updateOne() to replaceOne() to retain backwards compatibility.

在 Mongoose 8 中,不再支持 overwrite 选项。如果要覆盖整个文档,请使用 findOneAndReplace()replaceOne()

¥In Mongoose 8, the overwrite option is no longer supported. If you want to overwrite the entire document, use findOneAndReplace() or replaceOne().

使用 orFail() 和 upsert 更改了 findOneAndUpdate() 的行为

在 Mongoose 7 中,如果更新插入新文档,findOneAndUpdate(filter, update, { upsert: true }).orFail() 会抛出 DocumentNotFoundError。换句话说,如果没有找到文档,findOneAndUpdate().orFail() 总是会抛出错误,即使更新插入了新文档。

¥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.

在 Mongoose 8 中,findOneAndUpdate(filter, update, { upsert: true }).orFail() 总是成功。如果没有返回文档,findOneAndUpdate().orFail() 现在会抛出 DocumentNotFoundError,而不是没有找到文档。

¥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() 等待所有保存完成后再抛出任何错误

在 Mongoose 7 中,如果任何 save() 默认抛出错误,create() 会立即抛出。Mongoose 8 相反,会等待所有 save() 调用完成,然后再抛出发生的第一个错误。所以 create() 会在 Mongoose 7 和 Mongoose 8 中抛出相同的错误,Mongoose 8 可能需要更长的时间才能抛出错误。

¥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.

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() 返回对象的副本

在 Mongoose 7 中,Model.validate() 可能会修改传入的对象。Mongoose 8 首先复制传入的对象。

¥In Mongoose 7, Model.validate() would potentially modify the passed in object. Mongoose 8 instead copies the passed in object first.

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

允许 TypeScript 中的可选字段使用 null

在 Mongoose 8 中,TypeScript 中自动推断的模式类型允许 null 作为可选字段。在 Mongoose 7 中,可选字段只允许 undefined,不允许 null

¥In Mongoose 8, automatically inferred schema types in TypeScript allow null for optional fields. In Mongoose 7, optional fields only allowed undefined, not null.

const schema = new Schema({ name: String });
const TestModel = model('Test', schema);

const doc = new TestModel();

// In Mongoose 8, this type is `string | null | undefined`.
// In Mongoose 7, this type is `string | undefined`
doc.name;

模型构造函数属性在 TypeScript 中都是可选的

在 Mongoose 8 中,默认情况下模型构造函数不需要任何属性。

¥In Mongoose 8, no properties are required on model constructors by default.

import {Schema, model, Model} from 'mongoose';

interface IDocument {
  name: string;
  createdAt: Date;
  updatedAt: Date;
}

const documentSchema = new Schema<IDocument>(
  { name: { type: String, required: true } },
  { timestamps: true }
);

const TestModel = model<IDocument>('Document', documentSchema);

// Would throw a compile error in Mongoose 7, compiles in Mongoose 8
const newDoc = new TestModel({
  name: 'Foo'
});

// Explicitly pass generic param to constructor to specify the expected
// type of the model constructor param. The following will cause TS
// to complain about missing `createdAt` and `updatedAt` in Mongoose 8.
const newDoc2 = new TestModel<IDocument>({
  name: 'Foo'
});

从结构推断 distinct() 返回类型

interface User {
  name: string;
  email: string;
  avatar?: string;
}
const schema = new Schema<User>({
  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');