8000 Allow service methods to return a promise by daffl · Pull Request #59 · feathersjs/feathers · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/mixins/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

module.exports = [
require('./event')
require('./promise'),
require('./event')
];
28 changes: 28 additions & 0 deletions lib/mixins/promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

var _ = require('lodash');
var makeWrapper = function() {
return function() {
var result = this._super.apply(this, arguments);
var callback = arguments[arguments.length - 1];

if(typeof result !== 'undefined' && _.isFunction(result.then) && _.isFunction(callback)) {
result.then(function(data) {
callback(null, data);
}, function(error) {
callback(error);
});
}
return result;
};
};

module.exports = function (service) {
if (typeof service.mixin === 'function') {
var mixin = _.transform(_.pick(service, this.methods), function(result, num, key) {
result[key] = makeWrapper();
});

service.mixin(mixin);
}
};
2 changes: 2 additions & 0 deletions lib/providers/rest/wrappers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

var _ = require('lodash');

// A function that returns the middleware for a given method and service
Expand Down
2 changes: 2 additions & 0 deletions lib/providers/socket/commons.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

var _ = require('lodash');

// The position of the params parameters for a service method so that we can extend them
Expand Down
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@
"body-parser": "~1.0.2"
},
"devDependencies": {
"request": "~2.x",
"socket.io-client": "~0.9.0",
"grunt-cli": "~0.1.0",
"grunt": "~0.4.0",
"grunt-release": "~0.5.0",
"mocha": "~1.x",
"grunt-cli": "~0.1.0",
"grunt-contrib-jshint": "~0.x",
"grunt-jsbeautifier": "~0.2.0",
"grunt-release": "~0.5.0",
"grunt-simple-mocha": "~0.4.0",
"grunt-jsbeautifier": "~0.2.0"
"mocha": "~1.x",
"q": "~1.0.1",
"request": "~2.x",
"socket.io-client": "~0.9.0"
}
}
215 changes: 119 additions & 96 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,103 +171,9 @@ app.configure(feathers.primus({
}));
```

## API

### listen

`app.listen([port])` starts the application on the given port. It will first call the original [Express app.listen([port])](http://expressjs.com/api.html#app.listen), then run `app.setup(server)` (see below) with the server object and then return the server object.

### setup

`app.setup(server)` is used initialize all services by calling each services `.setup(app, path)` method (if available).
It will also use the `server` instance passed (e.g. through `http.createServer`) to set up SocketIO (if enabled) and any other provider that might require the server instance.

Normally `app.setup` will be called automatically when starting the application via `app.listen([port])` but there are cases when you need to initialize the server separately:

__HTTPS__

With your Feathers application initialized it is easy to set up an HTTPS REST and SocketIO server:

```js
app.configure(feathers.socketio()).use('/todos', todoService);

var https = require('https');
var server = https.createServer({
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
}, app).listen(443);

// Call app.setup to initialize all services and SocketIO
app.setup(server);
```

__Virtual Hosts__

You can use `feathers.vhost` (which is the same as [Express and Connect .vhost](http://www.senchalabs.org/connect/vhost.html)) to run your Feathers app on a virtual host:

```js
app.use('/todos', todoService);

var host = feathers().use(feathers.vhost('foo.com', app));
var server = host.listen(8080);

// Here we need to call app.setup because .listen on our virtal hosted
// app is never called
app.setup(server);
```

### lookup

`app.lookup(path)` returns the wrapped service object for the given path. Note that Feathers internally creates a new object from each registered service. This means that the object returned by `lookup` will provide the same methods and functionality as the original service but also functionality added by Feathers (most notably it is possible to listen to service events). `path` can be the service name with or without leading and trailing slashes.

```js
app.use('/my/todos', {
create: function(data, params, callback) {
callback(null, data);
}
});

var todoService = app.lookup('my/todos');
// todoService is an event emitter
todoService.on('created', function(todo) {
console.log('Created todo', todo);
});
```

### use

`app.use([path], service)` works just like [Express app.use([path], middleware)](http://expressjs.com/api.html#app.use) but additionally allows to register a service object (an object which at least provides one of the service methods as outlined in the Services section) instead of the middleware function. Note that REST services are registered in the same order as any other middleware so the below example will allow the `/todos` service only to [Passport](http://passportjs.org/) authenticated users.

```js
// Serve public folder for everybody
app.use(feathers.static(__dirname + '/public');
// Make sure that everything else only works with authentication
app.use(function(req,res,next){
if(req.isAuthenticated()){
next();
} else {
// 401 Not Authorized
next(new Error(401));
}
});
// Add a service.
app.use('/todos', {
get: function(name, params, callback) {
callback(null, {
id: name,
description: "You have to do " + name + "!"
});
}
});
```

### service

`app.service([path], service)` is what is called internally by `app.use([path], service)` if a service object is being passed. Use it instead of `app.use([path], service)` if you want to be more explicit that you are registering a service. `app.service` does __not__ provide the Express `app.use` functionality and doesn't check the service object for valid methods.

## Services

A service can be any JavaScript object that offers one or more of the `find`, `get`, `create`, `update`, `remove` and `setup` service methods with the following signatures:
As mentioned, the basic Feathers functionality is fully compatible with Express. The key concept added to that of middleware is *service objects. A service can be any JavaScript object that offers one or more of the `find`, `get`, `create`, `update`, `remove` and `setup` service methods with the following signatures:

```js
var myService = {
Expand All @@ -281,7 +187,30 @@ var myService = {
}
```

All callbacks follow the `function(error, data)` NodeJS convention. `params` can contain any additional parameters, for example the currently authenticated user. REST service calls set `params.query` with the query parameters (e.g. a query string like `?status=active&type=user` becomes `{ query: { status: "active", type: "user" } }`).
And can be used like any other Express middleware `app.use('/my-service', myService)`.

All service callbacks follow the `function(error, data)` NodeJS convention. `params` can contain any additional parameters, for example the currently authenticated user. REST service calls set `params.query` with the query parameters (e.g. a query string like `?status=active&type=user` becomes `{ query: { status: "active", type: "user" } }`).

It is also possible to return a [Promise](http://promises-aplus.github.io/promises-spec/) object from a service instead of using the callback, for example using [Q](https://github.com/kriskowal/q):

```js
var Q = require('q');

var todos = {
get: function(id) {
var dfd = Q.defer();

setTimeout(function() {
dfd.resolve({
id: id,
description: 'You have to do ' + id
});
}, 500);

return dfd.promise;
}
}
```

### find

Expand Down Expand Up @@ -626,6 +555,100 @@ socket.on('todo updated', function(data) {
});
```

## API

### listen

`app.listen([port])` starts the application on the given port. It will first call the original [Express app.listen([port])](http://expressjs.com/api.html#app.listen), then run `app.setup(server)` (see below) with the server object and then return the server object.

### setup

`app.setup(server)` is used initialize all services by calling each services `.setup(app, path)` method (if available).
It will also use the `server` instance passed (e.g. through `http.createServer`) to set up SocketIO (if enabled) and any other provider that might require the server instance.

Normally `app.setup` will be called automatically when starting the application via `app.listen([port])` but there are cases when you need to initialize the server separately:

__HTTPS__

With your Feathers application initialized it is easy to set up an HTTPS REST and SocketIO server:

```js
app.configure(feathers.socketio()).use('/todos', todoService);

var https = require('https');
var server = https.createServer({
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
}, app).listen(443);

// Call app.setup to initialize all services and SocketIO
app.setup(server);
```

__Virtual Hosts__

You can use `feathers.vhost` (which is the same as [Express and Connect .vhost](http://www.senchalabs.org/connect/vhost.html)) to run your Feathers app on a virtual host:

```js
app.use('/todos', todoService);

var host = feathers().use(feathers.vhost('foo.com', app));
var server = host.listen(8080);

// Here we need to call app.setup because .listen on our virtal hosted
// app is never called
app.setup(server);
```

### lookup

`app.lookup(path)` returns the wrapped service object for the given path. Note that Feathers internally creates a new object from each registered service. This means that the object returned by `lookup` will provide the same methods and functionality as the original service but also functionality added by Feathers (most notably it is possible to listen to service events). `path` can be the service name with or without leading and trailing slashes.

```js
app.use('/my/todos', {
create: function(data, params, callback) {
callback(null, data);
}
});

var todoService = app.lookup('my/todos');
// todoService is an event emitter
todoService.on('created', function(todo) {
console.log('Created todo', todo);
});
```

### use

`app.use([path], service)` works just like [Express app.use([path], middleware)](http://expressjs.com/api.html#app.use) but additionally allows to register a service object (an object which at least provides one of the service methods as outlined in the Services section) instead of the middleware function. Note that REST services are registered in the same order as any other middleware so the below example will allow the `/todos` service only to [Passport](http://passportjs.org/) authenticated users.

```js
// Serve public folder for everybody
app.use(feathers.static(__dirname + '/public');
// Make sure that everything else only works with authentication
app.use(function(req,res,next){
if(req.isAuthenticated()){
next();
} else {
// 401 Not Authorized
next(new Error(401));
}
});
// Add a service.
app.use('/todos', {
get: function(name, params, callback) {
callback(null, {
id: name,
description: "You have to do " + name + "!"
});
}
});
```

### service

`app.service([path], service)` is what is called internally by `app.use([path], service)` if a service object is being passed. Use it instead of `app.use([path], service)` if you want to be more explicit that you are registering a service. `app.service` does __not__ provide the Express `app.use` functionality and doesn't check the service object for valid methods.

## Why?

We know! Oh God another NodeJS framework! We really didn't want to add another name to the long list of NodeJS web frameworks but also wanted to explore a different approach than any other framework we have seen. We strongly believe that data is the core of the web and should be the focus of web applications.
Expand Down
27 changes: 27 additions & 0 deletions test/application.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var io = require('socket.io-client');
var request = require('request');
var https = require('https');
var fs = require('fs');
var q = require('q');

var feathers = require('../lib/feathers');

Expand Down Expand Up @@ -186,4 +187,30 @@ describe('Feathers application', function () {
});
});
});

it('returns the value of a promise (#41)', function (done) {
var original = {};
var todoService = {
get: function (name) {
original = {
id: name,
q: true,
description: "You have to do " + name + "!"
};
return q(original);
}
};

var app = feathers()
.configure(feathers.rest())
.use('/todo', todoService);

var server = app.listen(6880).on('listening', function () {
request('http://localhost:6880/todo/dishes', function (error, response, body) {
assert.ok(response.statusCode === 200, 'Got OK status code');
assert.deepEqual(original, JSON.parse(body));
server.close(done);
});
});
});
});
Loading
0