Demo of Async Virtuals in Monoose 6.2.7. Using no plugins, it's three steps.
-
Declare an instance method (it's
loadChildren
in the example). The instance method should store the results into a this._variable (variable name is not relevant, just be sure not to use one that's already in use). -
In
schema.post('find')
, run an Promise that fires your instance method in a loop using await Promise.all(). -
Use
schema.virtual('variable')
to declare a virtual type and return the populated "private" variable you declared in step one.
Adding virtuals: true
to your toObject
and toJSON
Schema options will cause your virtual to auto return when implementing.
- Note: the way this is setup,
document.loadChildren
will always fire. If this is unwanted, you could create aschema.pre('find')
that will look at the passed options and conditionally loadChildren in the post('find') middleware.
import mongoose from 'mongoose';
const { Schema } = mongoose;
class Comment extends Schema.Model {
async loadChildren() {
if (!this._id) throw new Error('No _id');
if (this._children) return this._children;
this._children = await mongoose.model('Comment').find({ parent: this._id });
return this._children;
}
}
const commentSchema = new Schema(
{
created: Date,
message: String,
modified: Date,
parent: Schema.Types.ObjectId,
title: String,
},
{
collection: 'comment',
timestamps: { createdAt: 'created', updatedAt: 'modified' },
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
commentSchema.post('find', async function (d) {
const ops = [];
for (const document of d) {
if (document.loadChildren) {
ops.push(document.loadChildren());
}
}
await Promise.all(ops);
});
commentSchema.virtual('children').get(function () {
return this._children;
});
commentSchema.loadClass(Comment);
const CommentModel = new model('Comment', commentSchema);
export { CommentModel };
A similar approach can be used for findOne.