Considering the code below, how can a transaction be implemented in order to ensure that someParentDocument doesn't get deleted and any operations performed inside the hooks are rolledback, when an error is thrown in any of the hooks?
const parentSchema = new mongoose.Schema({
name: String,
children: [{ type: mongoose.Schema.Types.ObjectId, ref: "Child" }],
});
const childSchema = new mongoose.Schema({
name: String,
parent: { type: mongoose.Schema.Types.ObjectId, ref: "Parent" },
});
parentSchema.pre("remove", async function(next){
// Add and remove documents to Parent and Child...
//...
next();
});
parentSchema.post("remove", async function(parent){
throw new Error("Exception!");
});
// (...)
try {
await someParentDocument.delete(); // <-- will trigger the hooks above
} catch {}
TLDR; Mongoose middleware was not designed for this.
This method of inserting transactions is actually patching the middleware functionality, and you are essentially creating an api completely separate from the
mongoosemiddleware.What would be better is inverting the logic for your remove query in a separate function.
Simple & Intended Solution
Allow a transaction handling method to do its magic, and create a separate remove method for your parent model. Mongoose wraps
mongodb.ClientSession.prototype.withTransactionwithmongoose.Connection.prototype.transactionand we don't even have to instantiate or manage a session! Look at the different between the length of this and that below. And you save the mental headache of remembering the internals of that middleware at the cost of one separate function.Small Extension
Another way to make this easy, is to register a middleware which simply inherits a session iff_ the query has one registered. Maybe throw an error if a transaction has not been started.
Risky & Complex Solution
This works, and is totally, horribly complex. Not recommended. Will likely break some day because it relies on intricacies of the mongoose API. I don't know why I coded this, please don't include it in your projects.