1

I'm trying to provide a collection having its methods wrapped in fibers for synchronous looking calls.

const Fiber = require('fibers');
const wrapInFiber = require('./wrapInFiber');

let db;
Fiber(function () {
  db = wrapInFiber({
    url: global.__MONGO_URI__,
    config: { useNewUrlParser: true, useUnifiedTopology: true },
    databaseName: global.__MONGO_DB_NAME__,
    collections: ['users'],
  });
  console.log('DB', db.users);
}).run();

module.exports.Users = db.users; <-- undefined

Console log outputs the value but db.users passed to the module.exports is undefined because either it runs before the fiber is done or rather outside the fiber, I presume? So, how can I pass the value to the module.exports correctly?

The only remotely close answer is this one which I tried to mimic but failed, maybe because his wrapped function involved no fibers related code unlike mine.

WrapInFiber just in case. It's adapted from this library.


const Fiber = require('fibers');
const { MongoClient } = require('mongodb');

module.exports = function ({ url, config, databaseName, collections }) {
  const Collection = function (db, name) {
    this.collection = db.collection(name);
  };

  Collection.prototype.count = function (query, options) {
    query = query || {};
    options = options || {};
    const fiber = Fiber.current;
    this.collection.count(query, options, function (err, records) {
      fiber.run(records);
    });
    return Fiber.yield();
  };

  Collection.prototype.find = function (query, options) {
    query = query || {};
    options = options || {};
    const fiber = Fiber.current;
    this.collection.find(query, options).toArray(function (err, records) {
      fiber.run(records);
    });
    return Fiber.yield();
  };

  Collection.prototype.findOne = function (query, options) {
    return this.find(query, options)[0];
  };

  Collection.prototype.update = function (query, options) {
    const fiber = Fiber.current;
    this.collection.update(query, options, function (err, records) {
      fiber.run(records);
    });
    return Fiber.yield();
  };

  Collection.prototype.insert = function (document) {
    const fiber = Fiber.current;
    this.collection.insert(document, function (err, records) {
      fiber.run(records);
    });
    return Fiber.yield();
  };

  Collection.prototype.remove = function (document) {
    const fiber = Fiber.current;
    this.collection.remove(document, function (err, records) {
      fiber.run(records);
    });
    return Fiber.yield();
  };

  const fiber = Fiber.current;

  MongoClient.connect(url, config, function (err, connection) {
    const obj = {
      close() {
        connection.close();
      },
    };
    const db = connection.db(databaseName);
    collections.forEach(function (collectionName) {
      obj[collectionName] = new Collection(db, collectionName);
    });
    fiber.run(obj);
  });

  return Fiber.yield();
};

Harry Adel
  • 1,238
  • 11
  • 16
  • wow, this looks really complicated. Why do you need this? DB collection methods in meteor already run in a fiber and are synchronous. So why can't you just use `Meteor.users.find()` directly? Why do you need your own version of fiber-wrapped DB queries? – Christian Fritz Nov 25 '20 at 00:35
  • Basically I'm trying to write tests for some Meteor package but the tests are being run without invoking the package rather importing the file because we're trying to migrate out of Meteor and I don't want to add any Meteor related code, so I need to stub out the collection in order to be able to invoke its methods like find, update, etc. For context: https://github.com/unchainedshop/unchained/pull/253/files#diff-6df3da198f1cf979745620d92e4c6427b2be079099b2fb03b1ac20ec4f3711cb – Harry Adel Nov 25 '20 at 08:34
  • I need to stub the Users collection to be able to test Roles.getUserRoles for instance https://github.com/unchainedshop/unchained/pull/253/files#diff-c21f2fc02211ac71c1abfa4bd9c7d9096284110f1e67d22361ac34578f21255bR159 – Harry Adel Nov 25 '20 at 08:44
  • @HarryAdel is there a reason, why you are not using the `Future` class as [proposed in the fibers documentation](https://github.com/laverdet/node-fibers#futures)? – Jankapunkt Nov 25 '20 at 09:53
  • How do you propose I use it? I tried something like this but it's not working. ``` const Fiber = require('fibers'); const Future = require('fibers/future'); const wrapInFiber = require('./wrapInFiber'); const fn = Future.wrap(wrapInFiber); const future = new Future(); Fiber(function () { const db = fn({ url: global.__MONGO_URI__, config: { useNewUrlParser: true, useUnifiedTopology: true }, databaseName: global.__MONGO_DB_NAME__, collections: ['users'], }).wait(); return future.return(db.users); }).run(); module.exports.Users = future; ``` – Harry Adel Nov 25 '20 at 10:12
  • I pushed my local changes. So now to test things out for yourselves all you have to do is clone the branch https://github.com/unchainedshop/unchained/pull/253 and run npx jest --runInBand --detectOpenHandles --forceExit tests/roles.test.js – Harry Adel Nov 25 '20 at 10:30

1 Answers1

0

While I didn't solve the issues of fibers I managed to solve the initial problem which led to this path of stubbing Meteor collections. All it took was a simple Sinon stub.

const sinon = require('sinon');
require('sinon-mongo');

const Users = sinon.mongo.collection({
  findOne: sinon
    .stub()
    .withArgs({ _id: 'user' }, { fields: { roles: 1 } })
    .returns({ _id: 'user', roles: [] }),
});

module.exports.Users = Users;

Harry Adel
  • 1,238
  • 11
  • 16