Not really. Although, to be fair, I’ve not looked!
As with all these things, portability comes with a price. In the case of Meteor, that price is increased verbosity and more upfront work. Depending on your appetite for that, you could consider making incremental changes towards portability and stopping when the returns diminish too far.
Fortunately, you can make a huge step just by removing meteorhacks:aggregate
and using the underlying MongoDB methods, some of which Meteor wraps for you. The meteorhacks:aggregate
package just adds another method to Meteor’s Mongo.Collection
object. It adds the underlying MongoDB library aggregate
method, wrapping its callback in Meteor.bindEnvironment
to make sure it’s Fiber-based. In principle, that’s fine - usage appears consistent and portable. Unfortunately, in the case of aggregate
, the type of result was changed from an array to an aggregation cursor, which the package (being unmaintained) does not support.
Let’s just discuss Fibers for a moment. I love them - they make coding so simple when they’re managed invisibly, as Meteor does for the most part. However, they’re not widely used in the vast majority of JavaScript projects. Also, they’re not available for the browser, since the code has to be built for the underlying OS, with all the various dependencies that needs. For portability today, that means using callbacks or Promises.
Let’s talk about callbacks. Not for long, because they’re horrible, but they are the JavaScript “old school” way of handling asynchronicity. As such, they’re supported everywhere. However, using callbacks in Meteor server code often gets you a “code must always run within a Fiber” error. The consequence of that is you need to wrap the callback in Meteor.bindEnvironment
or wrap the method in Meteor.wrapAsync
. Neither is particularly portable.
Onto Promises. You’ll probably already know I’m a fan - Promises were a stepping stone towards a coding style gaining widespread adoption. Of course, I’m referring to coding using async / await
. In many ways this brings Meteor’s groundbreaking sync-style of coding to JavaScript coders everywhere.
What’s this got to do with the MongoDB aggregate
method? Well, some time back all asynchronous MongoDB methods were refactored to support Promises. (They also continue to support callbacks - but let’s not go there.) Since they return Promises, we can adopt modern JavaScript coding styles - particularly async / await
to get code which is more portable very easily. Note, I did not say completely portable.
Portability 1
- Remove
meteorhacks:aggregate
and any import
s.
- Ensure you have
import { Promise } from 'meteor/promise';
in code using aggregation.
- Change
result = myCollection.aggregate(...);
to result = Promise.await(myCollection.rawCollection().aggregate(...).toArray());
As you will see, that’s not especially portable. However, it does get rid of meteorhacks:aggregate
. It relies on Meteor’s Promise package, which implements the (non-standard) Promise.await()
method and uses Fibers to hide its asynchronous nature. It also uses rawCollection()
.
Portability 2
This assumes you’re using a Meteor method to do the aggregation. I know Meteor methods are not portable, but this is an easy way to introduce async / await
.
-
Remove import { Promise } from 'meteor/promise';
unless you’re using explicit Promises somewhere else.
-
Change the method declaration to async
. So, for example:
Meteor.methods({
myMethod() {
// ...
return result;
},
});
becomes:
Meteor.methods({
async myMethod() {
// ...
return result;
},
});
-
Change result = Promise.await(myCollection.rawCollection().aggregate(...).toArray());
to result = await myCollection.rawCollection().aggregate(...).toArray();
That’s much better - we now only have rawCollection()
as a non-portable element.
Portability 3
We could choose to hide rawCollection().aggregate
by defining an aggregate
method on the collection (similar to meteorhacks:aggregate
). That would yield portable syntax for using the aggregate
method, when coupled with async / await
. Something like:
myCollection.aggregate = myCollection.rawCollection().aggregate.bind(myCollection.rawCollection());
If you have a single import
able file for the collection, that would be a good place.
Portability 4 and on
This is where it’s beyond the scope of a forum reply and beyond my willingness to go further
.