从 8.x 迁移到 9.x
¥Migrating from 8.x to 9.x
从 Mongoose 8.x 迁移到 Mongoose 9.x 时,你应该注意一些向后兼容性变更。
¥There are several backwards-breaking changes you should be aware of when migrating from Mongoose 8.x to Mongoose 9.x.
如果你仍在使用 Mongoose 7.x 或更早版本,请先阅读 Mongoose 7.x 到 8.x 迁移指南 并升级到 Mongoose 8.x,然后再升级到 Mongoose 9。
¥If you're still on Mongoose 7.x or earlier, please read the Mongoose 7.x to 8.x migration guide and upgrade to Mongoose 8.x first before upgrading to Mongoose 9.
Schema.prototype.doValidate() 现在返回一个 Promise
¥Schema.prototype.doValidate() now returns a promise
如果发生验证错误,Schema.prototype.doValidate() 现在会返回一个 Promise 对象并返回该 Promise 对象。在 Mongoose 8.x 中,doValidate() 接受一个回调函数,不再返回 Promise。
¥Schema.prototype.doValidate() now returns a promise that rejects with a validation error if one occurred.
In Mongoose 8.x, doValidate() took a callback and did not return a promise.
// Mongoose 8.x function signature
function doValidate(value, cb, scope, options) {}
// Mongoose 8.x example usage
schema.doValidate(value, function(error) {
if (error) {
// Handle validation error
}
}, scope, options);
// Mongoose 9.x function signature
async function doValidate(value, scope, options) {}
// Mongoose 9.x example usage
try {
await schema.doValidate(value, scope, options);
} catch (error) {
// Handle validation error
}
中间件函数中的错误优先级高于 next() 调用
¥Errors in middleware functions take priority over next() calls
在 Mongoose 8.x 中,如果中间件函数在调用 next() 后抛出错误,该错误将被忽略。
¥In Mongoose 8.x, if a middleware function threw an error after calling next(), that error would be ignored.
schema.pre('save', function(next) {
next();
// In Mongoose 8, this error will not get reported, because you already called next()
throw new Error('woops!');
});在 Mongoose 9 中,中间件函数中的错误优先级更高,因此上述 save() 操作会抛出错误。
¥In Mongoose 9, errors in the middleware function take priority, so the above save() would throw an error.
next() 不再支持向下一个中间件传递参数
¥next() no longer supports passing arguments to the next middleware
以前,你可以在钩子中调用 next(null, 'new arg'),而传递给下一个中间件的参数会被 '新增参数' 覆盖。
¥Previously, you could call next(null, 'new arg') in a hook and the args to the next middleware would get overwritten by 'new arg'.
schema.pre('save', function(next, options) {
options; // options passed to `save()`
next(null, 'new arg');
});
schema.pre('save', function(next, arg) {
arg; // In Mongoose 8, this would be 'new arg', overwrote the options passed to `save()`
});在 Mongoose 9 中,next(null, 'new arg') 不会覆盖传递给下一个中间件的参数。
¥In Mongoose 9, next(null, 'new arg') doesn't overwrite the args to the next middleware.
默认情况下不允许更新管道
¥Update pipelines disallowed by default
从 MongoDB 4.2 开始,你可以将管道阶段数组传递给 updateOne()、updateMany() 和 findOneAndUpdate(),以分阶段修改文档。Mongoose 完全不会对更新管道进行类型转换,因此在 Mongoose 9 中,我们默认将使用更新管道的行为抛出错误。
¥As of MongoDB 4.2, you can pass an array of pipeline stages to updateOne(), updateMany(), and findOneAndUpdate() to modify the document in multiple stages.
Mongoose does not cast update pipelines at all, so for Mongoose 9 we've made using update pipelines throw an error by default.
// Throws in Mongoose 9. Works in Mongoose 8
await Model.updateOne({}, [{ $set: { newProp: 'test2' } }]);设置 updatePipeline: true 以启用更新管道。
¥Set updatePipeline: true to enable update pipelines.
// Works in Mongoose 9
await Model.updateOne({}, [{ $set: { newProp: 'test2' } }], { updatePipeline: true });你还可以全局设置 updatePipeline,以便默认情况下为所有更新操作启用更新管道。
¥You can also set updatePipeline globally to enable update pipelines for all update operations by default.
// Enable update pipelines globally
mongoose.set('updatePipeline', true);
// Now update pipelines work without needing to specify the option on each query
await Model.updateOne({}, [{ $set: { newProp: 'test2' } }]);
// You can still override the global setting per query
await Model.updateOne({}, [{ $set: { newProp: 'test2' } }], { updatePipeline: false }); // throws
移除索引的 background 选项
¥Removed background option for indexes
从 MongoDB 4.2 开始,MongoDB 不再支持索引的 background 选项。Mongoose 9 不再默认设置 background 选项,并且不再支持在 Schema.prototype.index() 上设置 background 选项。
¥MongoDB no longer supports the background option for indexes as of MongoDB 4.2. Mongoose 9 will no longer set the background option by default and Mongoose 9 no longer supports setting the background option on Schema.prototype.index().
mongoose.isValidObjectId() 对数字返回 false
¥mongoose.isValidObjectId() returns false for numbers
在 Mongoose 8 中,你可以从一个数字创建一个新的 ObjectId,并且对于数字,isValidObjectId() 会返回 true。在 Mongoose 9 中,对于数字,isValidObjectId() 将返回 false,并且你无法再从数字创建新的 ObjectId。
¥In Mongoose 8, you could create a new ObjectId from a number, and isValidObjectId() would return true for numbers. In Mongoose 9, isValidObjectId() will return false for numbers and you can no longer create a new ObjectId from a number.
// true in mongoose 8, false in mongoose 9
mongoose.isValidObjectId(6);
// Works in Mongoose 8, throws in Mongoose 9
new mongoose.Types.ObjectId(6);
子文档 deleteOne() 钩子仅在子文档被删除时执行
¥Subdocument deleteOne() hooks execute only when subdocument is deleted
目前,对子文档调用 deleteOne() 会执行该子文档上的 deleteOne() hooks,无论该子文档是否实际被删除。
¥Currently, calling deleteOne() on a subdocument will execute the deleteOne() hooks on the subdocument regardless of whether the subdocument is actually deleted.
const SubSchema = new Schema({
myValue: {
type: String
}
}, {});
let count = 0;
SubSchema.pre('deleteOne', { document: true, query: false }, function(next) {
count++;
next();
});
const schema = new Schema({
foo: {
type: String,
required: true
},
mySubdoc: {
type: [SubSchema],
required: true
}
}, { minimize: false, collection: 'test' });
const Model = db.model('TestModel', schema);
const newModel = {
foo: 'bar',
mySubdoc: [{ myValue: 'some value' }]
};
const doc = await Model.create(newModel);
// In Mongoose 8, the following would trigger the `deleteOne` hook, even if `doc` is not saved or deleted.
doc.mySubdoc[0].deleteOne();
// In Mongoose 9, you would need to either `save()` or `deleteOne()` on `doc` to trigger the subdocument `deleteOne` hook.
await doc.save();
自定义方法和静态方法的钩子不再支持回调
¥Hooks for custom methods and statics no longer support callbacks
以前,你可以将 Mongoose 中间件与接受回调的自定义方法和静态方法一起使用。在 Mongoose 9 中,不再支持回调参数。如果你想将 Mongoose 中间件与自定义方法或静态方法一起使用,则该自定义方法或静态方法必须是异步函数或返回 Promise。
¥Previously, you could use Mongoose middleware with custom methods and statics that took callbacks. In Mongoose 9, this is no longer supported. If you want to use Mongoose middleware with a custom method or static, that custom method or static must be an async function or return a Promise.
const mySchema = new Schema({
name: String
});
// This is an example of a custom method that uses callbacks. While this method by itself still works in Mongoose 9,
// Mongoose 9 no longer supports hooks for this method.
mySchema.methods.foo = async function(cb) {
return cb(null, this.name);
};
mySchema.statics.bar = async function(cb) {
return cb(null, 'bar');
};
// This is no longer supported because `foo()` and `bar()` use callbacks.
mySchema.pre('foo', function() {
console.log('foo pre hook');
});
mySchema.pre('bar', function() {
console.log('bar pre hook');
});
// The following code has a custom method and a custom static that use async functions.
// The following works correctly in Mongoose 9: `pre('bar')` is executed when you call `bar()` and
// `pre('qux')` is executed when you call `qux()`.
mySchema.methods.baz = async function baz(arg) {
return arg;
};
mySchema.pre('baz', async function baz() {
console.log('baz pre hook');
});
mySchema.statics.qux = async function qux(arg) {
return arg;
};
mySchema.pre('qux', async function qux() {
console.log('qux pre hook');
});
Document.prototype.updateOne 不再接受回调函数
¥Document.prototype.updateOne no longer accepts a callback
Document.prototype.updateOne 在 Mongoose 8 中仍然支持回调函数。在 Mongoose 9 中,回调参数已被移除。
¥Document.prototype.updateOne still supported callbacks in Mongoose 8. In Mongoose 9, the callback parameter was removed.
const doc = await TestModel.findOne().orFail();
// Worked in Mongoose 8, no longer supported in Mongoose 9.
doc.updateOne({ name: 'updated' }, null, (err, res) => {
if (err) throw err;
console.log(res);
});
删除了 promiseOrCallback
¥Removed promiseOrCallback
Mongoose 9 移除了 promiseOrCallback 辅助函数。
¥Mongoose 9 removed the promiseOrCallback helper function.
const { promiseOrCallback } = require('mongoose');
promiseOrCallback; // undefined in Mongoose 9
isAsync 中间件不再受支持
¥isAsync middleware no longer supported
Mongoose 9 不再支持 isAsync 中间件。不支持同时使用 next 和 done 回调函数的旧签名中间件函数(即 function(next, done))。我们建议中间件现在使用 Promise 或 async/await。
¥Mongoose 9 no longer supports isAsync middleware. Middleware functions that use the legacy signature with both next and done callbacks (i.e., function(next, done)) are not supported. We recommend middleware now use promises or async/await.
如果你有使用 isAsync 中间件的代码,则必须将其重构为使用异步函数或返回 Promise。
¥If you have code that uses isAsync middleware, you must refactor it to use async functions or return a promise instead.
// ❌ Not supported in Mongoose 9
const schema = new Schema({});
schema.pre('save', true, function(next, done) {
execed.first = true;
setTimeout(
function() {
done(new Error('first done() error'));
},
5);
next();
});
schema.pre('save', true, function(next, done) {
execed.second = true;
setTimeout(
function() {
next(new Error('second next() error'));
done(new Error('second done() error'));
},
25);
});
// ✅ Supported in Mongoose 9: use async functions or return a promise
schema.pre('save', async function() {
execed.first = true;
await new Promise(resolve => setTimeout(resolve, 5));
});
schema.pre('save', async function() {
execed.second = true;
await new Promise(resolve => setTimeout(resolve, 25));
});
移除 skipOriginalStackTraces 选项
¥Removed skipOriginalStackTraces option
在 Mongoose 8 中,Mongoose 查询会存储一个 _executionStack 属性,用于记录查询最初执行位置的堆栈跟踪信息,以便调试 Query was already executed 错误。此行为可能会导致打包工具和源映射出现性能问题。添加 skipOriginalStackTraces 是为了解决此问题。在 Mongoose 9 中,由于 Mongoose 不再存储原始堆栈跟踪,因此不再需要此选项。
¥In Mongoose 8, Mongoose queries store an _executionStack property that stores the stack trace of where the query was originally executed for debugging Query was already executed errors.
This behavior can cause performance issues with bundlers and source maps.
skipOriginalStackTraces was added to work around this behavior.
In Mongoose 9, this option is no longer necessary because Mongoose no longer stores the original stack trace.
Node.js 版本支持
¥Node.js version support
Mongoose 9 需要 Node.js 18 或更高版本。
¥Mongoose 9 requires Node.js 18 or higher.
UUID 现在是 MongoDB UUID 对象
¥UUID's are now MongoDB UUID objects
Mongoose 9 现在将 UUID 对象作为 bson.UUID 的实例返回。在 Mongoose 8 中,UUID 是 Mongoose Buffer,通过 getter 方法转换为字符串。
¥Mongoose 9 now returns UUID objects as instances of bson.UUID. In Mongoose 8, UUIDs were Mongoose Buffers that were converted to strings via a getter.
const schema = new Schema({ uuid: 'UUID' });
const TestModel = mongoose.model('Test', schema);
const test = new TestModel({ uuid: new bson.UUID() });
await test.save();
test.uuid; // string in Mongoose 8, bson.UUID instance in Mongoose 9通过此更改,即使未设置 getters: true,UUID 也将以十六进制字符串格式在 JSON 中表示。
¥With this change, UUIDs will be represented in hex string format in JSON, even if getters: true is not set.
如果你希望默认情况下通过 getter 将 UUID 转换为字符串,可以使用 mongoose.Schema.Types.UUID.get():
¥If you want to convert UUIDs to strings via a getter by default, you can use mongoose.Schema.Types.UUID.get():
// Configure all UUIDs to have a getter which converts the UUID to a string
mongoose.Schema.Types.UUID.get(v => v == null ? v : v.toString());
const schema = new Schema({ uuid: 'UUID' });
const TestModel = mongoose.model('Test', schema);
const test = new TestModel({ uuid: new bson.UUID() });
await test.save();
test.uuid; // string
移除 SchemaType 的 caster 和 casterConstructor 属性
¥SchemaType caster and casterConstructor properties were removed
在 Mongoose 8 中,某些模式类型实例具有一个 caster 属性,其中包含嵌入式模式类型或嵌入式子文档构造函数。在 Mongoose 9 中,为了使类型和内部逻辑更加一致,我们移除了 caster 属性,转而使用 embeddedSchemaType 和 Constructor。
¥In Mongoose 8, certain schema type instances had a caster property which contained either the embedded schema type or embedded subdocument constructor.
In Mongoose 9, to make types and internal logic more consistent, we removed the caster property in favor of embeddedSchemaType and Constructor.
const schema = new mongoose.Schema({ docArray: [new mongoose.Schema({ name: String })], arr: [String] });
// In Mongoose 8:
console.log(schema.path('arr').caster); // SchemaString
console.log(schema.path('docArray').caster); // EmbeddedDocument constructor
console.log(schema.path('arr').casterConstructor); // SchemaString constructor
console.log(schema.path('docArray').casterConstructor); // EmbeddedDocument constructor
// In Mongoose 9:
console.log(schema.path('arr').embeddedSchemaType); // SchemaString
console.log(schema.path('docArray').embeddedSchemaType); // SchemaDocumentArrayElement
console.log(schema.path('arr').Constructor); // undefined
console.log(schema.path('docArray').Constructor); // EmbeddedDocument constructor在 Mongoose 8 中,还有一个内部 $embeddedSchemaType 属性。该属性已被 embeddedSchemaType 取代,embeddedSchemaType 现在是公共 API 的一部分。
¥In Mongoose 8, there was also an internal $embeddedSchemaType property. That property has been replaced with embeddedSchemaType, which is now part of the public API.
移除 Model() 和 Document() 的 skipId 参数
¥Removed skipId parameter to Model() and Document()
在 Mongoose 8 中,Model() 和 Document() 的第三个参数可以是布尔值或 options 对象。如果是布尔值,Mongoose 会将第三个参数解释为 skipId 选项。在 Mongoose 9 中,第三个参数始终是 options 对象,不再支持传递 boolean。
¥In Mongoose 8, the 3rd parameter to Model() and Document() was either a boolean or options object.
If a boolean, Mongoose would interpret the 3rd parameter as the skipId option.
In Mongoose 9, the 3rd parameter is always an options object, passing a boolean is no longer supported.
移除查询中的 use$geoWithin 参数,现在始终为 true
¥Query use$geoWithin removed, now always true
mongoose.Query 有一个 use$geoWithin 属性,可以配置将 $geoWithin 转换为 $within,以支持 MongoDB 2.4 之前的版本。该属性已在 Mongoose 9 中移除。$geoWithin 现在不再转换为 $within,因为 MongoDB 不再支持 $within。
¥mongoose.Query had a use$geoWithin property that could configure converting $geoWithin to $within to support MongoDB versions before 2.4.
That property has been removed in Mongoose 9. $geoWithin is now never converted to $within, because MongoDB no longer supports $within.
从 useDb()/connections 中移除 noListener 选项
¥Removed noListener option from useDb()/connections
noListener 选项已从连接和 useDb() 方法中移除。在 Mongoose 8.x 中,你可以使用 { noListener: true } 参数调用 useDb(),以防止新的连接对象监听基础连接的状态变化。这在为每个请求动态创建连接时,有时有助于减少内存使用量。
¥The noListener option has been removed from connections and from the useDb() method. In Mongoose 8.x, you could call useDb() with { noListener: true } to prevent the new connection object from listening to state changes on the base connection, which was sometimes useful to reduce memory usage when dynamically creating connections for every request.
在 Mongoose 9.x 中,不再支持或记录 noListener 选项。useDb() 的第二个参数现在仅支持 { useCache }。
¥In Mongoose 9.x, the noListener option is no longer supported or documented. The second argument to useDb() now only supports { useCache }.
// Mongoose 8.x
conn.useDb('myDb', { noListener: true }); // works
// Mongoose 9.x
conn.useDb('myDb', { noListener: true }); // TypeError: noListener is not a supported option
conn.useDb('myDb', { useCache: true }); // works
TypeScript
FilterQuery 已重命名为 QueryFilter
¥FilterQuery renamed to QueryFilter
在 Mongoose 9 中,FilterQuery(Model.find()、Model.findOne() 等的第一个参数)被重命名为 QueryFilter。
¥In Mongoose 9, FilterQuery (the first parameter to Model.find(), Model.findOne(), etc.) was renamed to QueryFilter.
QueryFilter 属性不再解析为 any
¥QueryFilter Properties No Longer Resolve to any
在 Mongoose 9 中,作为 Model.find()、Model.findOne() 等的第一个参数类型的 QueryFilter 类型现在对顶层键强制执行更强的类型。
¥In Mongoose 9, the QueryFilter type, which is the type of the first param to Model.find(), Model.findOne(), etc. now enforces stronger types for top-level keys.
const schema = new Schema({ age: Number });
const TestModel = mongoose.model('Test', schema);
TestModel.find({ age: 'not a number' }); // Works in Mongoose 8, TS error in Mongoose 9
TestModel.find({ age: { $notAnOperator: 42 } }); // Works in Mongoose 8, TS error in Mongoose 9如果你在创建查询时使用泛型(如下例所示),则此变更会造成向后兼容性变更。如果你遇到以下问题或任何类似问题,可以使用 as QueryFilter。
¥This change is backwards breaking if you use generics when creating queries as shown in the following example.
If you run into the following issue or any similar issues, you can use as QueryFilter.
// From https://stackoverflow.com/questions/56505560/how-to-fix-ts2322-could-be-instantiated-with-a-different-subtype-of-constraint:
// "Never assign a concrete type to a generic type parameter, consider it as read-only!"
// This function is generally something you shouldn't do in TypeScript, can work around it with `as` though.
function findById<ModelType extends {_id: Types.ObjectId | string}>(model: Model<ModelType>, _id: Types.ObjectId | string) {
return model.find({_id: _id} as QueryFilter<ModelType>); // In Mongoose 8, this `as` was not required
}
不再为 create() 和 insertOne() 设置通用参数
¥No more generic parameter for create() and insertOne()
在 Mongoose 8 中,create() 和 insertOne() 接受泛型参数,这意味着 TypeScript 允许你将任何值传递给函数。
¥In Mongoose 8, create() and insertOne() accepted a generic parameter, which meant TypeScript let you pass any value to the function.
const schema = new Schema({ age: Number });
const TestModel = mongoose.model('Test', schema);
// Worked in Mongoose 8, TypeScript error in Mongoose 9
const doc = await TestModel.create({ age: 'not a number', someOtherProperty: 'value' });在 Mongoose 9 中,create() 和 insertOne() 不再接受泛型参数。取而代之的是,他们接受 Partial<RawDocType> 选项,并应用了一些额外的查询类型转换,允许使用对象作为映射,字符串作为 ObjectId,以及 POJO 作为子文档和文档数组。
¥In Mongoose 9, create() and insertOne() no longer accept a generic parameter. Instead, they accept Partial<RawDocType> with some additional query casting applied that allows objects for maps, strings for ObjectIds, and POJOs for subdocuments and document arrays.
如果传递给 create() 的参数与 Partial<RawDocType> 不匹配,则可以使用 as 进行类型转换,如下所示。
¥If your parameters to create() don't match Partial<RawDocType>, you can use as to cast as follows.
const doc = await TestModel.create({ age: 'not a number', someOtherProperty: 'value' } as unknown as Partial<InferSchemaType<typeof schema>>);
文档 id 不再是 any
¥Document id is no longer any
在 Mongoose 8 及更早版本中,id 是 Document 类的一个属性,其值设置为 any。这与运行时行为不一致,运行时中 id 是一个虚拟属性,它返回字符串 _id,除非模式中已经存在 id 属性,或者模式的 id 选项设置为 false。
¥In Mongoose 8 and earlier, id was a property on the Document class that was set to any.
This was inconsistent with runtime behavior, where id is a virtual property that returns _id as a string, unless there is already an id property on the schema or the schema has the id option set to false.
Mongoose 9 将 id 作为字符串属性添加到 TVirtuals。Document 类不再包含 id 属性。
¥Mongoose 9 appends id as a string property to TVirtuals. The Document class no longer has an id property.
const schema = new Schema({ age: Number });
const TestModel = mongoose.model('Test', schema);
const doc = new TestModel();
doc.id; // 'string' in Mongoose 9, 'any' in Mongoose 8.