從 7.x 遷移至 8.x

從 Mongoose 7.x 遷移至 Mongoose 8.x 時,您應該注意幾個向後不相容的變更。

如果您仍在使用 Mongoose 6.x 或更早的版本,請先閱讀Mongoose 6.x 至 7.x 遷移指南,並升級至 Mongoose 7.x,然後再升級至 Mongoose 8。

我們也建議您在升級至 Mongoose 8 之前,先檢閱 MongoDB Node.js 驅動程式 v6.0.0 的版本說明

移除 findOneAndUpdate()rawResult 選項

findOneAndUpdate()findOneAndReplace()findOneAndDelete()rawResult 選項已被 includeResultMetadata 選項取代。

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 完全相同。

Document.prototype.deleteOne 現在會回傳一個查詢

在 Mongoose 7 中,doc.deleteOne() 會回傳一個解析為 doc 的 Promise。在 Mongoose 8 中,doc.deleteOne() 會回傳一個查詢,以便更輕鬆地進行鏈結,並與 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

  1. ObjectId 建構函式不再接受長度為 12 的字串。在 Mongoose 7 中,new mongoose.Types.ObjectId('12charstring') 完全有效。在 Mongoose 8 中,new mongoose.Types.ObjectId('12charstring') 會拋出錯誤。

  2. 已移除已棄用的 SSL 選項

    • sslCA -> tlsCAFile
    • sslCRL -> tlsCRLFile
    • sslCert -> tlsCertificateKeyFile
    • sslKey -> tlsCertificateKeyFile
    • sslPass -> tlsCertificateKeyFilePassword
    • sslValidate -> tlsAllowInvalidCertificates
    • tlsCertificateFile -> tlsCertificateKeyFile

移除 findOneAndRemove()

在 Mongoose 7 中,findOneAndRemove()findOneAndDelete() 的別名,Mongoose 為了向後相容性而支援它。Mongoose 8 不再支援 findOneAndRemove()。請改用 findOneAndDelete()

移除 count()

Mongoose 8 中移除了 Model.count()Query.prototype.count()。請改用 Model.countDocuments()Query.prototype.countDocuments()

移除 id 設定器

在 Mongoose 7.4 中,Mongoose 引入了一個 id 設定器,使 doc.id = '0'.repeat(24) 等同於 doc._id = '0'.repeat(24)。在 Mongoose 8 中,該設定器現在已移除。

null 對於非必要字串列舉值有效

在 Mongoose 8 之前,將具有 enum 的字串路徑設定為 null 會導致驗證錯誤,即使該路徑不是 required。在 Mongoose 8 中,如果未設定 required,即使具有 enum,也可以將字串路徑設定為 null

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 只會在儲存新文件時套用最小化,而不會在更新現有文件時套用。

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 之前執行。


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 會跳過在 $set 中包裝 update 參數,這表示 findOneAndUpdate()update() 會覆寫相符的文件。在 Mongoose 7 中,設定 overwrite 會將 findOneAndUpdate() 轉換為 findOneAndReplace(),並將 updateOne() 轉換為 replaceOne(),以保留向後相容性。

在 Mongoose 8 中,不再支援 overwrite 選項。如果您想要覆寫整個文件,請使用 findOneAndReplace()replaceOne()

變更具有 orFail() 和 upsert 的 findOneAndUpdate() 行為

在 Mongoose 7 中,如果 upsert 了新文件,findOneAndUpdate(filter, update, { upsert: true }).orFail() 會拋出 DocumentNotFoundError。換句話說,如果找不到任何文件,即使 upsert 了新文件,findOneAndUpdate().orFail() 也總是會拋出錯誤。

在 Mongoose 8 中,findOneAndUpdate(filter, update, { upsert: true }).orFail() 總是會成功。現在,如果沒有回傳任何文件,findOneAndUpdate().orFail() 就會拋出 DocumentNotFoundError,而不是如果找不到任何文件。

Create 等到所有儲存完成後才拋出任何錯誤

在 Mongoose 7 中,如果任何 save() 預設拋出錯誤,create() 會立即拋出錯誤。Mongoose 8 則會等待所有 save() 呼叫完成後,再拋出第一個發生的錯誤。因此,create() 在 Mongoose 7 和 Mongoose 8 中都會拋出相同的錯誤,Mongoose 8 可能只是需要更長的時間才能拋出錯誤。

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 則會先複製傳入的物件。

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

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 中,預設情況下,模型建構函式上不需要任何屬性。

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