diff --git a/.gitignore b/.gitignore
index 824d1909c3..45b6f40851 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-tmp*
\ No newline at end of file
+tmp*
+node_modules/
+npm-debug.log
\ No newline at end of file
diff --git a/example/custom_service/app.js b/example/custom_service/app.js
new file mode 100644
index 0000000000..fa4f7183dd
--- /dev/null
+++ b/example/custom_service/app.js
@@ -0,0 +1,43 @@
+var feathry = require('../../lib/feathry');
+var Proto = require('uberproto');
+var users = [
+ {
+ id: '1',
+ name : 'Wayne Campbell',
+ slogan: 'Party on Garth'
+ },
+ {
+ id: '2',
+ name : 'Garth Algar',
+ slogan: 'Party on Wayne'
+ }
+];
+
+var service = {
+ index : function (params, cb) {
+ cb(null, users);
+ },
+
+ create: function(data, params, cb) {
+ console.log(data, params);
+ users.push(data);
+ cb(null, data);
+ },
+
+ get : function(id, params, cb) {
+ for (var user in users){
+ if (users[user] && users[user].id === id) {
+ return cb(null, users[user]);
+ }
+ }
+
+ cb(new Error('User With ID '+ id +' Not Found'));
+ }
+};
+
+Proto.mixin(require('../../lib/mixin/event'), service);
+
+feathry.createServer({ port: 8000 })
+ .service('users', service)
+ .provide(feathry.rest())
+ .start();
\ No newline at end of file
diff --git a/example/rest_memory/app.js b/example/rest_memory/app.js
new file mode 100644
index 0000000000..decdb2af3b
--- /dev/null
+++ b/example/rest_memory/app.js
@@ -0,0 +1,10 @@
+var feathry = require('../../lib/feathry');
+var Proto = require('uberproto');
+var memoryService = feathry.memory();
+
+Proto.mixin(require('../../lib/mixin/event'), memoryService);
+
+feathry.createServer({ port: 8000 })
+ .service('users', memoryService)
+ .provide(feathry.rest())
+ .start();
\ No newline at end of file
diff --git a/example/rest_memory/index.html b/example/rest_memory/index.html
new file mode 100644
index 0000000000..f2a0c98277
--- /dev/null
+++ b/example/rest_memory/index.html
@@ -0,0 +1,17 @@
+
+
+
+ Feathry REST Memory Example
+
+
+ Feathry REST Memory Example
+
+
+ - Actions
+ - GET /users
+ - POST /users
+ - PUT /users/1
+ - DELETE /users/1
+
+
+
\ No newline at end of file
diff --git a/lib/errors.js b/lib/errors.js
index 9dab4036a8..01226ebba8 100644
--- a/lib/errors.js
+++ b/lib/errors.js
@@ -1,8 +1,9 @@
function createError(name, defaultMessage) {
- function Err(message, data) {
+ function Err(message, data, statusCode) {
this.name = this.type = name;
- this.message = message || defaultMessage || "Unknown error";
+ this.message = message || defaultMessage || "Internal Server Error";
this.data = data;
+ this.status = statusCode || 500;
}
Err.prototype = Object.create(Error);
@@ -23,6 +24,7 @@ var ValidationError = exports.ValidationError = function Err(message, data) {
this.name = this.type = 'ValidationError';
this.message = message;
this.data = data;
-}
+ this.status = 400;
+};
ValidationError.prototype = Object.create(Error);
diff --git a/lib/feathry.js b/lib/feathry.js
index 6f67c360b4..30c806c1d0 100644
--- a/lib/feathry.js
+++ b/lib/feathry.js
@@ -1,6 +1,7 @@
var server = require('./server');
var _ = require('underscore');
var providers = require('./providers');
+var services = require('./services');
exports.errors = require('./errors');
exports.Server = server.Server;
@@ -9,5 +10,13 @@ exports.createServer = server.createServer;
_.each(providers, function(Provider, name) {
exports[name] = function(options) {
return Provider.create(options);
- }
+ };
+});
+
+_.each(services, function(Service, name) {
+ exports[name] = function(options) {
+ // TODO: Fix this! Can't call create because UberProto
+ // calls create which we are overriding in our service
+ return Service.create(options);
+ };
});
diff --git a/lib/server.js b/lib/server.js
index 35b5abea83..716b15acf6 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -59,4 +59,4 @@ var Server = Proto.extend({
exports.Server = Server;
exports.createServer = function (options) {
return Server.create(options);
-}
+};
diff --git a/lib/services/index.js b/lib/services/index.js
new file mode 100644
index 0000000000..4d73ddd66d
--- /dev/null
+++ b/lib/services/index.js
@@ -0,0 +1,6 @@
+module.exports = {
+ memory: require('./memory')
+ // mongo: require('./mongo'),
+ // redis: require('./redis'),
+ // couch: require('./couch'),
+}
\ No newline at end of file
diff --git a/lib/service/memory.js b/lib/services/memory.js
similarity index 95%
rename from lib/service/memory.js
rename to lib/services/memory.js
index 42f9bc1d89..c9f27d7f59 100644
--- a/lib/service/memory.js
+++ b/lib/services/memory.js
@@ -16,9 +16,9 @@ var mutators = {
__limit: function (values, param) {
return values.slice(0, param);
}
-}
+};
-module.exports = Proto.extend({
+var MemoryService = Proto.extend({
init: function (idField) {
this._id = idField || 'id';
this._uId = 0;
@@ -68,3 +68,5 @@ module.exports = Proto.extend({
cb('Could not find record with ' + id);
}
});
+
+module.exports = MemoryService;
diff --git a/lib/services/mongo.js b/lib/services/mongo.js
new file mode 100644
index 0000000000..53371be8bb
--- /dev/null
+++ b/lib/services/mongo.js
@@ -0,0 +1,73 @@
+var Proto = require('uberproto');
+var error = require('../errors');
+var mongo = require('mongoskin');
+var _ = require('underscore');
+var mutators = {
+ __sort: function (values, param) {
+ return _.sortBy(values, function (item) {
+ return item[param];
+ });
+ },
+ __order: function (values) {
+ return values.reverse();
+ },
+ __offset: function (values, param) {
+ return values.slice(param);
+ },
+ __limit: function (values, param) {
+ return values.slice(0, param);
+ }
+};
+
+var MongoService = Proto.extend({
+ init: function (idField) {
+ this._id = idField || '_id';
+ this._uId = 0;
+ this.store = {};
+ },
+
+ index: function (params, cb) {
+ var values = _.values(this.store);
+
+ _.each(mutators, function(handler, name) {
+ values = params[name] ? handler(values, params[name]) : values;
+ });
+
+ cb(null, values);
+ },
+
+ get: function (id, params, cb) {
+ if (id in this.store) {
+ cb(null, this.store[id]);
+ return;
+ }
+ cb(new error.NotFound('Could not find record', { id: id }));
+ },
+
+ create: function (data, params, cb) {
+ var id = data[this._id] || this._uId++;
+ data[this._id] = id;
+ this.store[id] = data;
+ cb(null, data);
+ },
+
+ update: function (id, data, cb) {
+ if (id in this.store) {
+ this.store[id] = data;
+ cb(null, store[id]);
+ return;
+ }
+ cb('Could not find record with ' + id);
+ },
+
+ destroy: function (id, params, cb) {
+ if (id in this.store) {
+ var deleted = this.store[id];
+ delete this.store[id];
+ return cb(null, deleted);
+ }
+ cb('Could not find record with ' + id);
+ }
+});
+
+module.exports = MongoService;
diff --git a/license.md b/license.md
index 0338c818d3..1c3ec09843 100644
--- a/license.md
+++ b/license.md
@@ -1,4 +1,5 @@
Copyright (C) 2013 David Luecke daff@neyeon.com
+Copyright (C) 2013 Eric Kryski e.kryski@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/package.json b/package.json
index 5641a4351b..8e54dc63ac 100644
--- a/package.json
+++ b/package.json
@@ -8,15 +8,20 @@
"url": "git://github.com/daffl/feathry.git"
},
"author": "David Luecke (http://neyeon.com)",
+ "contributors": [
+ "Eric Kryski (http://erickryski.com)"
+ ],
"main": "lib/feathry",
"directories": {
"lib": "lib"
},
"scripts": {
+ "start": "node example/rest/app.js",
"test": "nodeunit test"
},
"engines": {
- "node": "*"
+ "node": "*",
+ "npm": "*"
},
"dependencies": {
"uberproto": ">= 1.0.0",
diff --git a/readme.md b/readme.md
index b52e674533..a586122da1 100644
--- a/readme.md
+++ b/readme.md
@@ -1,29 +1,59 @@
# Feathry
-A resource service framework for easily creating REST and SocketIO APIs with NodeJS.
+#### An ultra scalable, data oriented framework built for tomorrow's web.
-## Get started
+The core focus of Feathry is your **data**. We believe that ultimately your app's purpose is to manage data in some fashion and so that's all you should really need to deal with. Managing your data. Feathry provides a deadly simple way of managing your data and allows you to provide this data via REST and SocketIO APIs with NodeJS.
-Services are just simple objects that provide certain methods. This makes it easy to initialize a
-service that provides a single todo:
+## Why Another NodeJS Framework?
+
+We know... Oh God another bloody NodeJS framework! Yes we are also very tired of seeing all these NodeJS frameworks. All the rails clones are getting a bit boring and really aren't taking advantage of the real strengths of NodeJS. We wanted to take a different approach than every other framework we have seen, because we believe that data is core to the web and should be the core focus of web applications.
+
+We also think that your data resources can and should be encapsulated in such a way that they can be ultra scalable and self contained. The MVC pattern works well but it is becoming antiquated in today's web. Frankly you don't need it and they tend to become bloated.
+
+With that being said there are some amazing frameworks already out there and we wanted to leverage the ideas that have been put into them, which is why Feathry is built on top of [Express](http://expressjs.com) and is inspired in part by [Flatiron](http://flatironjs.org) and [Derby](http://derbyjs.com).
+
+
+## Key Concepts
+
+At the core to Feathry are 3 simple but important concepts, **Providers**, **Services** and **Mixins**.
+
+A **Provider** is simply a module that *provides* your data to clients (ie. via REST or Web Sockets).
+
+A **Service** is a module that is used by the provider to actually manage the data (ie. a database adapter)
+
+A **Mixin** is like a utility that you can use to improve your service (ie. validation or authentication)
+
+*TODO: Maybe think of a different name for service*
+
+
+## Getting Started is Easy
+
+Like we said, services are just simple modules that expose certain methods to the providers. This makes it easy to initialize a service that say... provides a single TODO:
```js
var feathry = require('feathry');
+var todoService = {
+ get: function(name, params, callback) {
+ callback(null, {
+ id: name,
+ description: "You have to do " + name + "!"
+ });
+ }
+};
+
feathry.createServer({ port: 8000 })
- .service('todo', {
- get: function(name, params, callback) {
- callback(null, {
- id: name,
- description: "You have to do " + name + "!"
- });
- }
- })
+ .service('todo', todoService)
.provide(feathry.rest())
.provide(feathry.socketio())
.start();
```
+That's all there really is to building an app with Feathry... Providers, Services, and Mixins!
+
+
+## Built In Providers
+
### REST
You can access the REST service by going to `http://localhost:8000/todo/dishes` in your browser
@@ -36,7 +66,7 @@ and will see:
### SocketIO
-Since you added it as a provider, you can also connect to your service via SocketIO.
+Since, in the above example, you added it as a provider, you can also connect to your service via SocketIO.
Create an HTML page and insert the following code to see the response data logged on the console:
```html
@@ -79,9 +109,15 @@ All callbacks follow the `function(error, data)` NodeJS convention.
### setup(server)
-## Built in services
+## Built In Services
+
+### Memory
+
+### MongoDB (TODO)
+
+### Redis (TODO)
-## Service mixins
+## Built In Service Mixins
### Events
diff --git a/test/service/memory.js b/test/service/memory.js
index 5ed99385ac..0ad2697b43 100644
--- a/test/service/memory.js
+++ b/test/service/memory.js
@@ -2,7 +2,7 @@ var assert = require('assert');
var MemoryService = require('../../lib/service/memory');
var Proto = require('uberproto');
-describe('Memory service', function () {
+describe('Memory Service', function () {
it('create', function (done) {
var service = Proto.create.call(MemoryService);
service.create({
diff --git a/test/service/mongo.js b/test/service/mongo.js
new file mode 100644
index 0000000000..92c0dc4f6b
--- /dev/null
+++ b/test/service/mongo.js
@@ -0,0 +1,50 @@
+var assert = require('assert');
+var MongoService = require('../../lib/service/mongo');
+var Proto = require('uberproto');
+
+describe('Mongo Service', function () {
+ it('create', function (done) {
+ var service = Proto.create.call(MongoService);
+ service.create({
+ name: 'Test 1'
+ }, {}, function(error, data) {
+ assert.equal(data.id, 0);
+ assert.equal(data.name, 'Test 1');
+ done();
+ });
+ });
+
+ it('index', function(done) {
+ var service = Proto.create.call(MongoService);
+ for(var i = 0; i < 20; i++) {
+ service.create({
+ name: 'Test ' + i
+ }, {}, function(error, data) {
+
+ });
+ }
+ });
+
+ it('creates indexes and gets items', function (done) {
+ var service = Proto.create.call(MongoService);
+ service.create({
+ name: 'Test 1'
+ }, {}, function() {
+ service.create({
+ name: 'Test 2'
+ }, {}, function(error, data) {
+ assert.equal(data.id, 1);
+ assert.equal(data.name, 'Test 2');
+ service.index({}, function(error, items) {
+ assert.ok(Array.isArray(items));
+ assert.equal(items.length, 2);
+ service.get(0, {}, function(error, data) {
+ assert.equal(data.id, 0);
+ assert.equal(data.name, 'Test 1');
+ done();
+ });
+ });
+ });
+ });
+ });
+});