Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions docs/migrating_to_9.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,14 @@ const { promiseOrCallback } = require('mongoose');
promiseOrCallback; // undefined in Mongoose 9
```

## In `isAsync` middleware `next()` errors take priority over `done()` errors
## `isAsync` middleware no longer supported

Due to Mongoose middleware now relying on promises and async/await, `next()` errors take priority over `done()` errors.
If you use `isAsync` middleware, any errors in `next()` will be thrown first, and `done()` errors will only be thrown if there are no `next()` errors.
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.

If you have code that uses `isAsync` middleware, you must refactor it to use async functions or return a promise instead.

```javascript
// ❌ Not supported in Mongoose 9
const schema = new Schema({});

schema.pre('save', true, function(next, done) {
Expand All @@ -214,8 +216,16 @@ schema.pre('save', true, function(next, done) {
25);
});

// In Mongoose 8, with the above middleware, `save()` would error with 'first done() error'
// In Mongoose 9, with the above middleware, `save()` will error with 'second next() error'
// ✅ 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));
});
```

## Removed `skipOriginalStackTraces` option
Expand Down
9 changes: 3 additions & 6 deletions lib/helpers/timestamps/setupTimestamps.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,13 @@ module.exports = function setupTimestamps(schema, timestamps) {

schema.add(schemaAdditions);

schema.pre('save', function timestampsPreSave(next) {
schema.pre('save', function timestampsPreSave() {
const timestampOption = get(this, '$__.saveOptions.timestamps');
if (timestampOption === false) {
return next();
return;
}

setDocumentTimestamps(this, timestampOption, currentTime, createdAt, updatedAt);

next();
});

schema.methods.initializeTimestamps = function() {
Expand Down Expand Up @@ -88,7 +86,7 @@ module.exports = function setupTimestamps(schema, timestamps) {
schema.pre('updateOne', opts, _setTimestampsOnUpdate);
schema.pre('updateMany', opts, _setTimestampsOnUpdate);

function _setTimestampsOnUpdate(next) {
function _setTimestampsOnUpdate() {
const now = currentTime != null ?
currentTime() :
this.model.base.now();
Expand All @@ -105,6 +103,5 @@ module.exports = function setupTimestamps(schema, timestamps) {
replaceOps.has(this.op)
);
applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
next();
}
};
1 change: 0 additions & 1 deletion lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2914,7 +2914,6 @@ Model.insertMany = async function insertMany(arr, options) {
await this._middleware.execPost('insertMany', this, [arr], { error });
}


options = options || {};
const ThisModel = this;
const limit = options.limit || 1000;
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/validateBeforeSave.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

module.exports = function validateBeforeSave(schema) {
const unshift = true;
schema.pre('save', false, async function validateBeforeSave(_next, options) {
schema.pre('save', false, async function validateBeforeSave(options) {
// Nested docs have their own presave
if (this.$isSubdocument) {
return;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"type": "commonjs",
"license": "MIT",
"dependencies": {
"kareem": "git+https://github.com/mongoosejs/kareem.git#vkarpov15/v3",
"kareem": "git+https://github.com/mongoosejs/kareem.git#vkarpov15/remove-isasync",
"mongodb": "~6.18.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
Expand Down
27 changes: 13 additions & 14 deletions test/aggregate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,9 +886,9 @@ describe('aggregate: ', function() {
const s = new Schema({ name: String });

let called = 0;
s.pre('aggregate', function(next) {
s.pre('aggregate', function() {
++called;
next();
return Promise.resolve();
});

Copy link
Preview

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Returning Promise.resolve() is unnecessary for synchronous operations. Either declare the function as async or simply remove the return statement since the middleware doesn't need to return anything for synchronous operations.

Suggested change
});

Copilot uses AI. Check for mistakes.

const M = db.model('Test', s);
Expand All @@ -902,9 +902,9 @@ describe('aggregate: ', function() {
it('setting option in pre (gh-7606)', async function() {
const s = new Schema({ name: String });

s.pre('aggregate', function(next) {
s.pre('aggregate', function() {
this.options.collation = { locale: 'en_US', strength: 1 };
next();
return Promise.resolve();
});
Copy link
Preview

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Returning Promise.resolve() is unnecessary for synchronous operations. Either declare the function as async or simply remove the return statement since the middleware doesn't need to return anything for synchronous operations.

Copilot uses AI. Check for mistakes.


const M = db.model('Test', s);
Expand All @@ -920,9 +920,9 @@ describe('aggregate: ', function() {
it('adding to pipeline in pre (gh-8017)', async function() {
const s = new Schema({ name: String });

s.pre('aggregate', function(next) {
s.pre('aggregate', function() {
this.append({ $limit: 1 });
next();
return Promise.resolve();
});
Copy link
Preview

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Returning Promise.resolve() is unnecessary for synchronous operations. Either declare the function as async or simply remove the return statement since the middleware doesn't need to return anything for synchronous operations.

Copilot uses AI. Check for mistakes.


const M = db.model('Test', s);
Expand Down Expand Up @@ -980,8 +980,8 @@ describe('aggregate: ', function() {
const s = new Schema({ name: String });

const calledWith = [];
s.pre('aggregate', function(next) {
next(new Error('woops'));
s.pre('aggregate', function() {
return Promise.reject(new Error('woops'));
Copy link
Preview

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Instead of returning a rejected promise, use throw new Error('woops') for consistency with the new error handling pattern established in this PR.

Suggested change
return Promise.reject(new Error('woops'));
throw new Error('woops');

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if it'll make a difference to throw an error within a synchronous function compared to an async function. Will both errors be handled the same way?

});
s.post('aggregate', function(error, res, next) {
calledWith.push(error);
Expand All @@ -1003,9 +1003,9 @@ describe('aggregate: ', function() {

let calledPre = 0;
let calledPost = 0;
s.pre('aggregate', function(next) {
s.pre('aggregate', function() {
++calledPre;
next();
return Promise.resolve();
});
Copy link
Preview

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Returning Promise.resolve() is unnecessary for synchronous operations. Either declare the function as async or simply remove the return statement since the middleware doesn't need to return anything for synchronous operations.

Suggested change
});
});

Copilot uses AI. Check for mistakes.

s.post('aggregate', function(res, next) {
++calledPost;
Expand All @@ -1030,9 +1030,9 @@ describe('aggregate: ', function() {

let calledPre = 0;
const calledPost = [];
s.pre('aggregate', function(next) {
s.pre('aggregate', function() {
++calledPre;
next();
return Promise.resolve();
});
Copy link
Preview

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Returning Promise.resolve() is unnecessary for synchronous operations. Either declare the function as async or simply remove the return statement since the middleware doesn't need to return anything for synchronous operations.

Suggested change
});
});

Copilot uses AI. Check for mistakes.

s.post('aggregate', function(res, next) {
calledPost.push(res);
Expand Down Expand Up @@ -1295,11 +1295,10 @@ describe('aggregate: ', function() {
it('cursor() errors out if schema pre aggregate hook throws an error (gh-15279)', async function() {
const schema = new Schema({ name: String });

schema.pre('aggregate', function(next) {
schema.pre('aggregate', function() {
if (!this.options.allowed) {
throw new Error('Unauthorized aggregate operation: only allowed operations are permitted');
}
next();
});

const Test = db.model('Test', schema);
Expand Down
6 changes: 2 additions & 4 deletions test/docs/discriminators.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,15 @@ describe('discriminator docs', function() {

const eventSchema = new mongoose.Schema({ time: Date }, options);
let eventSchemaCalls = 0;
eventSchema.pre('validate', function(next) {
eventSchema.pre('validate', function() {
++eventSchemaCalls;
next();
});
const Event = mongoose.model('GenericEvent', eventSchema);

const clickedLinkSchema = new mongoose.Schema({ url: String }, options);
let clickedSchemaCalls = 0;
clickedLinkSchema.pre('validate', function(next) {
clickedLinkSchema.pre('validate', function() {
++clickedSchemaCalls;
next();
});
const ClickedLinkEvent = Event.discriminator('ClickedLinkEvent',
clickedLinkSchema);
Expand Down
3 changes: 1 addition & 2 deletions test/document.modified.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,8 @@ describe('document modified', function() {
});

let preCalls = 0;
childSchema.pre('save', function(next) {
childSchema.pre('save', function() {
++preCalls;
next();
});

let postCalls = 0;
Expand Down
Loading
Loading