feathersjs-ecosystem / feathers-waterline

A Feathers adapter for the Waterline ORM
MIT License
21 stars 5 forks source link

Feathers Waterline service #22

Closed kokujin closed 8 years ago

kokujin commented 8 years ago

I need some help using feathers-waterline with other services. I have scaffolded an app with a few services that use NeDB. I would like a particular service to use Waterline. From the docs, I came up with this:

'use strict';

const Waterline = require('waterline');
const diskAdapter = require('sails-disk');
const service = require('feathers-waterline');
const hooks = require('./hooks');

const ORM = new Waterline();

// CONFIGURATION
// ------------------------------------------------------------------------------
const config = {
  adapters: {
    'default': diskAdapter,
    disk: diskAdapter
  },
  connections: {
    myLocalDisk: {
      adapter: 'disk'
    }
  },
  defaults: {
    migrate: 'alter'
  }
};

// Model
// ------------------------------------------------------------------------------
const Thing = Waterline.Collection.extend({
  identity: 'thing',
  schema: true,
  connection: 'myLocalDisk',
  attributes: {
    text: {
      type: 'string',
      required: true
    },

    complete: {
      type: 'boolean'
    }
  }
});

// ------------------------------------------------------------------------------

module.exports = function() {
  const app = this;
  ORM.loadCollection(Thing);
  app.use('/things', service(config));

  ORM.initialize(config, function(error, data) {
    //console.log(data.collections.thing);
    if (error) {
      console.error(error);
    }

    // Create a Waterline Feathers service with a default page size of 2 items
    // and a maximum size of 4
    app.use('/things', service({
      Model: data.collections.thing,
      paginate: {
        default: 2,
        max: 4
      }
    }));
  });

  // Get our initialize service to that we can bind hooks
  const thingsService = app.service('/things');

  //console.log('thingsService: ', thingsService);

  // Set up our before hooks
  thingsService.before(hooks.before);

  // Set up our after hooks
  thingsService.after(hooks.afte);
};

This errors out with this trace

Feathers application started on localhost:3030
error: Route: /things - Cannot read property 'count' of undefined
info: TypeError: Cannot read property 'count' of undefined

Can someone tell me what I am doing wrong? Thanks

daffl commented 8 years ago

If you set the DEBUG=feathers* environment variable you should get a little more info. My guess is that the errors is in https://github.com/feathersjs/feathers-waterline/blob/master/src/index.js#L23 which means that data.collections.thing is potentially undefined.

kokujin commented 8 years ago

I set the mode to debug, no further information, here is the traceback

error: Route: /things - Cannot read property 'count' of undefined
info: TypeError: Cannot read property 'count' of undefined
    at Object._find (/home/kokujin/development/research/fw_test/node_modules/feathers-waterline/lib/index.js:66:31)
    at Object.find (/home/kokujin/development/research/fw_test/node_modules/feathers-waterline/lib/index.js:102:25)
    at /home/kokujin/development/research/fw_test/node_modules/feathers-hooks/lib/hooks.js:74:31
    at new Promise (/home/kokujin/development/research/fw_test/node_modules/core-js/modules/es6.promise.js:191:7)
    at /home/kokujin/development/research/fw_test/node_modules/feathers-hooks/lib/hooks.js:58:16
    at run (/home/kokujin/development/research/fw_test/node_modules/core-js/modules/es6.promise.js:87:22)
    at /home/kokujin/development/research/fw_test/node_modules/core-js/modules/es6.promise.js:100:28
    at flush (/home/kokujin/development/research/fw_test/node_modules/core-js/modules/_microtask.js:18:9)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)

I logged out the Model to see if it was being created, it is.. Could it be that feathers service is having a problem wiith a waterline variant?

daffl commented 8 years ago

Are you sure that the model you are passing (data.collections.thing) isn't undefined? To me that's the only explanation for this issue.

daffl commented 8 years ago

So it looks like you are registering the things service twice. Once with the most likely wrong configuration in

  ORM.loadCollection(Thing);
  app.use('/things', service(config));

And then later with the correct settings as

    app.use('/things', service({
      Model: data.collections.thing,
      paginate: {
        default: 2,
        max: 4
      }
    }));

It is probably trying to use the first one.

kokujin commented 8 years ago

Thanks @daffl for your patience.

This is the error that gets traced using only one config, the second call using service

/home/kokujin/development/research/fw_test/src/services/things/index.js:76
  thingsService.before(hooks.before);
               ^

TypeError: Cannot read property 'before' of undefined
    at EventEmitter.module.exports (/home/kokujin/development/research/fw_test/src/services/things/index.js:76:16)
    at EventEmitter.configure (/home/kokujin/development/research/fw_test/node_modules/feathers/lib/application.js:138:8)
    at EventEmitter.module.exports (/home/kokujin/development/research/fw_test/src/services/index.js:13:7)
    at EventEmitter.configure (/home/kokujin/development/research/fw_test/node_modules/feathers/lib/application.js:138:8)
    at Object.<anonymous> (/home/kokujin/development/research/fw_test/src/app.js:30:4)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Module.require (module.js:468:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/kokujin/development/research/fw_test/src/index.js:3:13)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
daffl commented 8 years ago

It's because you are now retrieving the service before it is registered. Your code has to look like this:

'use strict';

const Waterline = require('waterline');
const diskAdapter = require('sails-disk');
const service = require('feathers-waterline');
const hooks = require('./hooks');

const ORM = new Waterline();

// CONFIGURATION
// ------------------------------------------------------------------------------
const config = {
  adapters: {
    'default': diskAdapter,
    disk: diskAdapter
  },
  connections: {
    myLocalDisk: {
      adapter: 'disk'
    }
  },
  defaults: {
    migrate: 'alter'
  }
};

// Model
// ------------------------------------------------------------------------------
const Thing = Waterline.Collection.extend({
  identity: 'thing',
  schema: true,
  connection: 'myLocalDisk',
  attributes: {
    text: {
      type: 'string',
      required: true
    },

    complete: {
      type: 'boolean'
    }
  }
});

// ------------------------------------------------------------------------------

module.exports = function() {
  const app = this;
  ORM.loadCollection(Thing);

  ORM.initialize(config, function(error, data) {
    //console.log(data.collections.thing);
    if (error) {
      console.error(error);
    }

    // Create a Waterline Feathers service with a default page size of 2 items
    // and a maximum size of 4
    app.use('/things', service({
      Model: data.collections.thing,
      paginate: {
        default: 2,
        max: 4
      }
    }));

    // Get our initialize service to that we can bind hooks
    const thingsService = app.service('/things');

    //console.log('thingsService: ', thingsService);

    // Set up our before hooks
    thingsService.before(hooks.before);

    // Set up our after hooks
    thingsService.after(hooks.after);
  });
};
kokujin commented 8 years ago

I had tried that out too, but reverted because it causes an error

Feathers application started on localhost:3030
info: (404) Route: /things - Page not found
daffl commented 8 years ago

It's probably because you registered a notFound the asynchronous service initialization. Just like any other Express middleware the order matters so you have to make sure that any middleware that should run at the end is registered after everything else.

kokujin commented 8 years ago

That cant be the case @daffl , I have 2 services scaffolded with the feathers CLI so that I can compare. Services that use NeDB and Mongoose work.

I suspect a bug, I'll have to step through the code with a debugger perhaps. You could try out the code too to verify what I am seeing.

I am using a freshly scaffolded feathers app for testing. Nothing has been modified.

daffl commented 8 years ago

NeDB and Mongoose are not initialized asynchronously so all services are registered before the not found handler. For Waterline you need to configure middleware after the asynchronous database initialization:

module.exports = function() {
  const app = this;
  ORM.loadCollection(Thing);

  ORM.initialize(config, function(error, data) {
    //console.log(data.collections.thing);
    if (error) {
      console.error(error);
    }

    // Create a Waterline Feathers service with a default page size of 2 items
    // and a maximum size of 4
    app.use('/things', service({
      Model: data.collections.thing,
      paginate: {
        default: 2,
        max: 4
      }
    }));

    // Get our initialize service to that we can bind hooks
    const thingsService = app.service('/things');

    //console.log('thingsService: ', thingsService);

    // Set up our before hooks
    thingsService.before(hooks.before);

    // Set up our after hooks
    thingsService.after(hooks.after);

    // Configure middleware here
    app.configure(middleware());

    // Or middleware individually
    app.use(notFound);
  });
};
daffl commented 8 years ago

That clunky initialization is the reason why feathers-waterline and feathers-mongodb can't be selected in the app generator at the moment.

kokujin commented 8 years ago

You are right, Waterline is initialized differently, and it isindeed clunky. Thanks for your help.

daffl commented 8 years ago

The new generator creates an app that supports this initialization mode so hopefully it'll be easier to deal with then. For now you'll probably have to somehow save the state of the asynchronous initialization somewhere and then run app.configure(middleware()); (and other things that need to go last) when it completes.

kokujin commented 8 years ago

That would be great, when would the new generator hit the repository?

Thanks.