Browse files

[major] Remove Resource abstraction to narrow down the focus of the l…

…ibrary

Fixes #15
  • Loading branch information...
1 parent be3ec1a commit 3180c8af0826a6c11f7df96f0b4bbb52787b59ff @3rd-Eden 3rd-Eden committed Nov 13, 2013
Showing with 4 additions and 780 deletions.
  1. +0 −50 SPEC/Acl.md
  2. +2 −4 index.js
  3. +0 −2 page.js
  4. +2 −9 pagelet.js
  5. +0 −389 resource.js
  6. +0 −35 shared.js
  7. +0 −1 test/common.js
  8. +0 −5 test/pipe.test.js
  9. +0 −285 test/resource.test.js
View
50 SPEC/Acl.md
@@ -1,50 +0,0 @@
-# Acl
-
-The Access Control List or `ACL` provides logic for granting and revoking access
-to `Resources`. In most circumstances you'll only need a single `ACL` per `Pipe`
-instance. The `ACL` can be used to assert which resources can be accessed by a
-Pagelet `Pipe` has one `ACL` instance by default with a reference to the
-resource pool.
-
-```js
-var ACL = require('acl')
- , Pool = require('pool');
-
-var acl = new ACL({
- resources: new Pool({ type: 'resources' })
-});
-```
-
-#### Logic
-
-Before the `ACL` actually does anything, resources have to be granted access to
-specific grantees. This access can also be revoked at any point. `assert` can be
-called to check if the grantee is allowed to access the resource.
-
-```js
-acl.grant('guest', 'register'); // register is an actual pagelet.
-acl.revoke('guest', 'register'); // don't allow registering anymore.
-```
-
-- Grantees can be anything from generalized roles (e.g. admin, guest), unique ids
- or specific user names.
-- Resources are unique strings representing anything from pagelets to actual
- resources. If the resource can be found in the pool an additional assert
- function will be called on the resource.
-
-#### Authorization
-
-Each `Pagelet` has an authorize method which can extended with custom code, the
-methods provided by the `ACL` or a mix of both. Since assert returns a Boolean
-it works out of the box with authorize.
-
-```js
-Pagelet.extend({
- name: 'register'
-
- , authorize: function (req, done) {
- // assertion checks if the guest is allowed to register
- this.pipe.acl.assert('guest', 'register', done);
- }
-});
-```
View
6 index.js
@@ -16,10 +16,9 @@ var debug = require('debug')('bigpipe:server')
// Library internals.
//
var Compiler = require('./lib/compiler')
- , Resource = require('./resource')
, Pagelet = require('./pagelet')
- , Page = require('./page')
- , shared = require('./shared');
+ , shared = require('./shared')
+ , Page = require('./page');
//
// Try to detect if we've got domains support. So we can easily serve 500 error
@@ -891,7 +890,6 @@ Pipe.createServer = function createServer(port, options) {
//
// Expose our constructors.
//
-Pipe.Resource = Resource;
Pipe.Pagelet = Pagelet;
Pipe.Page = Page;
View
2 page.js
@@ -1051,14 +1051,12 @@ Page.extend = require('extendable');
Page.on = function on(module) {
var dir = this.prototype.directory = this.prototype.directory || path.dirname(module.filename)
, pagelets = this.prototype.pagelets
- , resources = this.prototype.resources
, resolve = this.prototype.resolve;
//
// Resolve pagelets and resource paths.
//
if (pagelets) Object.keys(pagelets).forEach(resolve(dir, pagelets));
- if (resources) Object.keys(resources).forEach(resolve(dir, resources));
module.exports = this;
return this;
View
11 pagelet.js
@@ -371,16 +371,9 @@ Pagelet.extend = require('extendable');
// ```
//
Pagelet.on = function on(module) {
- var dir = this.prototype.directory = this.prototype.directory || path.dirname(module.filename)
- , resources = this.prototype.resources
- , resolve = this.prototype.resolve;
-
- //
- // Resolve resource paths.
- //
- if (resources) Object.keys(resources).forEach(resolve(dir, resources));
-
+ this.prototype.directory = this.prototype.directory || path.dirname(module.filename);
module.exports = this;
+
return this;
};
View
389 resource.js
@@ -1,389 +0,0 @@
-'use strict';
-
-var shared = require('./shared')
- , rest = ['get', 'post', 'put', 'delete'];
-
-function Resource() {
- if (!(this instanceof Resource)) return new Resource();
-}
-
-Resource.prototype = Object.create(require('stream').prototype, shared.mixin({
- constructor: {
- value: Resource,
- writable: true,
- enumerable: false,
- configurable: true
- },
-
- /**
- * Simple lookup table that caches responses so we're not doing duplicate
- * lookups of data. If people don't want to have their shit cached. They
- * should set it to `undefined` explicitly.
- *
- * @type {Array}
- * @public
- */
- cache: {
- value: [],
- writable: true,
- enumerable: false,
- configurable: true
- },
-
- /**
- * Simple state that people can manipulate which persists until the resource
- * has been destroyed.
- *
- * @type {Mixed}
- * @public
- */
- state: {
- value: null,
- writable: true,
- enumerable: false,
- configurable: true
- },
-
- //
- // !IMPORTANT
- //
- // Function's should never overridden as we might depend on them internally,
- // that's why they are configured with writable: false and configurable: false
- // by default.
- //
- // !IMPORTANT
- //
-
- /**
- * GET proxy, which will call the provided or default GET and
- * emit the READ status on completion. This requires any custom state methods
- * to accept a callback as last argument.
- *
- * @param {String} method GET, POST, PUT, DELETE
- * @api private
- */
- proxyMethod: {
- enumerable: false,
- value: function proxyMethod(method) {
- var self = this;
-
- return function callback() {
- var args = Array.prototype.slice.apply(arguments)
- , fn = args.pop();
-
- //
- // Call cache proxy and provide custom callback to excert control.
- //
- args.push(self.proxy(fn));
- if (method in self) return self['_' + method].apply(self, args);
-
- //
- // Call the REST method if implemented or return the callback with error.
- //
- self.proxy(fn).call(self, new Error(
- 'unable to call ' + method + ' on the resource'
- ));
- };
- }
- },
-
- /**
- * Proxy method to channel all callbacks from resources through, this will
- * expose Error objects only if manipulation of resources fails and will make
- * sure callbacks are async.
- *
- * @param {Funtion} fn callback
- * @api private
- */
- proxy: {
- enumerable: false,
- value: function proxy(fn) {
- /**
- * Last callback before arguments are returned to the orginal callee.
- *
- * @param {Mixed} error error message
- * @param {Mixed} data
- * @api private
- */
- return function final(error, data) {
- if (error && !(error instanceof Error)) error = new Error(error);
-
- //
- // Defer callbacks so resource are always async.
- //
- process.nextTick(function callback() {
- fn(error, data);
- });
- };
- }
- },
-
- /**
- * Return array indices which have object in accordance with the object
- *
- * @param {Object} query
- * @returns {Array} of indices
- * @api private
- */
- find: {
- enumerable: false,
- value: function find(query) {
- var cache = this.cache
- , indices = [];
-
- //
- // Return empty list of indices if nothing is cached.
- //
- if ('object' !== typeof query || !cache) return indices;
-
- //
- // Extract matching indices from the cache.
- //
- return cache.reduce(function where(list, object, i) {
- var matches = Object.keys(query).reduce(function check(result, control) {
- result.push(control in object && query[control] === object[control]);
- return result;
- }, []);
-
- //
- // Only include the indice if every queried key matched.
- //
- if(matches.length === matches.filter(Boolean).length) list.push(i);
- return list;
- }, []);
- }
- },
-
- /**
- * Return objects in cache that match the indices in the list.
- *
- * @param {Array} list of indices
- * @returns {Array} cached objects in correspondence with indices
- * @api private
- */
- aquire: {
- enumerable: false,
- value: function aquire(list) {
- return (this.cache || []).reduce(function filter(stack, value, i) {
- if (~list.indexOf(i)) stack.push(value);
- return stack;
- }, []);
- }
- },
-
- /**
- * GET from cache or proxy to user implemented GET method.
- *
- * @type {Function}
- * @api private
- */
- _get: {
- enumerable: false,
- value: function _get(query, fn) {
- var self = this
- , cache = self.aquire(self.find(query));
-
- //
- // Values were found in cache return cached values.
- //
- if (cache.length) return fn(null, cache);
-
- //
- // Tiny middleware function to populate cache on callback.
- //
- self.get(query, function push(error, data) {
- if (error || !data) return fn(error, data);
-
- //
- // Check if equal objects where added to the cache before, if not push.
- //
- data.forEach(function locate(q) {
- if (self.find(q).length === 0) cache.push(q);
- });
-
- fn(error, data);
- });
- }
- },
-
- /**
- * POST a new value to the resource.
- *
- * @type {Function}
- * @api private
- */
- _post: {
- enumerable: false,
- value: function _post(data, fn) {
- var cache = this.cache;
-
- //
- // Only insert data in cache if the actual provided POST succeeds. This
- // will ensure more cache consistency.
- //
- this.post(data, function push(error, result) {
- if (cache.length && !error) cache.push(data);
- fn(error, !error && result);
- });
- }
- },
-
- /**
- * PUT a value in the resource.
- *
- * @type {Function}
- * @public
- */
- _put: {
- enumerable: false,
- value: function _put(query, data, fn) {
- var self = this
- , cache = self.cache;
-
- //
- // Update the or append to the resources.
- //
- self.put(query, data, function update(error, result) {
- if (cache.length && !error) {
- var indices = self.find(query);
-
- //
- // Update the cache, append if the query returned no indices.
- //
- if (!indices.length) {
- cache.push(data);
- } else {
- indices.forEach(function merge(key) {
- self.cache[key] = self.merge(cache[key], data);
- });
- }
- }
-
- fn(error, !error && result);
- });
- }
- },
-
- /**
- * DELETE a value from the resource.
- *
- * @type {Function}
- * @public
- */
- _delete: {
- enumerable: false,
- value: function _deleted(query, fn) {
- var self = this
- , cache = self.cache;
-
- //
- // Delete queried indices from the cache if resources where deleted from
- // the external resource as well.
- //
- self.delete(query, function deleted(error, result) {
- var indices = self.find(query);
-
- //
- // Delete indices from cache.
- //
- if (cache.length && !error) {
- indices.forEach(function purge(key) { delete cache[key]; });
- self.cache = cache.filter(Boolean);
- }
-
- fn(error, !error && result);
- });
- }
- },
-
- /**
- * Invalidate the cache all other get requests will now bypass the cache. It
- * needs to set the cache to a `false` value. We're not allowed to set it to
- * `undefined` as that's the indication that the user doesn't want to use any
- * resource caching.
- *
- * @api private
- */
- invalidate: {
- enumerable: false,
- value: function invalidate() {
- this.cache = null;
- }
- },
-
- /**
- * Pull data from the resource once and remove it.
- *
- * @param {Mixed} data The data that we want to retrieve and delete.
- * @param {Function} fn The callback.
- * @api public
- */
- pull: {
- enumerable: false,
- value: function pull(data, fn) {
- var resource = this;
-
- this.get(data, function get(err, found) {
- if (!found) return fn(err, found);
-
- resource.delete(data, function deleted(fail) {
- fn(err || fail, found);
- });
- });
- }
- },
-
- /**
- * Configure the resource.
- *
- * @api private
- */
- configure: {
- enumerable: false,
- value: function configure(req, res) {
- var self = this;
-
- //
- // Listen to each REST event and delegate it to our private functions,
- // which can then call the user defined REST actions if provided.
- //
- this.removeAllListeners();
- rest.forEach(function initRest(method) {
- self.on(method, self.proxyMethod(method));
- });
-
- //
- // Supply an empty array to cache, since previous use could have unset it.
- //
- this.cache = [];
-
- if (this.initialize) this.initialize(req, res);
- else if (this.initialise) this.initialise(req, res);
- }
- }
-}));
-
-//
-// Make the Resource extendable.
-//
-Resource.extend = require('extendable');
-
-//
-// Expose the Resource on the exports through the same interface as we're doing
-// with Pagelet and Page constructors
-//
-// ```js
-// Resource.extend({
-// ..
-// }).on(module);
-// ```
-//
-Resource.on = function on(module) {
- module.exports = this;
- return this;
-};
-
-//
-// Initialize.
-//
-module.exports = Resource;
View
35 shared.js
@@ -10,19 +10,6 @@ var path = require('path');
var shared = {
/**
- * List of resources that can be used by the pagelets.
- *
- * @type {object}
- * @public
- */
- resources: {
- value: {},
- writable: true,
- enumerable: false,
- configurable: true
- },
-
- /**
* Simple emit wrapper that returns a function that emits an event once it's
* called
*
@@ -87,28 +74,6 @@ var shared = {
},
/**
- * Access a resource.
- *
- * @TODO re-use previous initialised resources.
- * @param {String} name The resource.
- * @api public
- */
- resource: {
- enumerable: false,
- value: function get(name) {
- var page = this.page || this
- , Resource = this.resources[name] || page.resources[name]
- , resource;
-
- if ('string' === typeof Resource) Resource = require(Resource);
- resource = new Resource;
-
- resource.configure(page.req, page.res);
- return resource;
- }
- },
-
- /**
* Recursively merge properties of two objects.
*
* @param {Object} a first object
View
1 test/common.js
@@ -9,7 +9,6 @@ chai.Assertion.includeStack = true;
exports.Pipe = require('../');
exports.Pagelet = require('../pagelet');
exports.Page = require('../page');
-exports.Resource = require('../resource');
exports.shared = require('../shared');
//
View
5 test/pipe.test.js
@@ -37,11 +37,6 @@ describe('Pipe', function () {
expect(Pipe.Pagelet.extend).to.be.a('function');
});
- it('exposes the Resource constructor', function () {
- expect(Pipe.Resource).to.be.a('function');
- expect(Pipe.Resource.extend).to.be.a('function');
- });
-
it('is an EvenEmitter3', function () {
expect(app).to.be.instanceOf(require('eventemitter3'));
});
View
285 test/resource.test.js
@@ -1,285 +0,0 @@
-describe('Resource', function () {
- 'use strict';
-
- var common = require('./common')
- , expect = common.expect
- , Resource = common.Resource
- , resource;
-
- beforeEach(function () {
- resource = new Resource;
- resource.configure();
- });
-
- afterEach(function () {
- resource = null;
- });
-
- it('resource listens to GET event', function (done) {
- resource.emit('get', { id: 1 }, function (err, data) {
- expect(err).to.be.an.instanceof(Error);
- expect(err.message).to.equal('unable to call get on the resource');
- expect(data).to.equal(undefined);
- done();
- });
- });
-
- it('resource listens to POST event', function (done) {
- resource.emit('post', { hello: 'world' }, function (err, result) {
- expect(err).to.be.an.instanceof(Error);
- expect(err.message).to.equal('unable to call post on the resource');
- expect(result).to.equal(undefined);
- done();
- });
- });
-
- it('resource listens to PUT event', function (done) {
- resource.emit('put', { hello: 'world' }, { id: 1 }, function (err, result) {
- expect(err).to.be.an.instanceof(Error);
- expect(err.message).to.equal('unable to call put on the resource');
- expect(result).to.equal(undefined);
- done();
- });
- });
-
- it('resource listens to DELETE event', function (done) {
- resource.emit('delete', { id: 1 }, function (err, result) {
- expect(err).to.be.an.instanceof(Error);
- expect(err.message).to.equal('unable to call delete on the resource');
- expect(result).to.equal(undefined);
- done();
- });
- });
-
- describe('#find', function () {
- it('returns empty list if cache is unavailable', function () {
- resource.cache = null;
-
- var cached = resource.find({ random: 'query' });
- expect(cached).to.be.an('array');
- expect(cached.length).to.equal(0);
- });
-
- it('returns empty list if query is not of type Object', function () {
- resource.cache = [{ random: 'data' }];
-
- var cached = resource.find('query');
- expect(cached).to.be.an('array');
- expect(cached.length).to.equal(0);
- });
-
- it('returns indices of objects matching query', function () {
- resource.cache = [{ name: 'Jake' }, { name: 'Jeff' }, { name: 'Jake' }];
-
- var cached = resource.find({ name: 'Jake' });
- expect(cached).to.be.an('array');
- expect(cached.length).to.equal(2);
- expect(cached).to.include(0);
- expect(cached).to.include(2);
- });
-
- it('returns empty indices list if restrictive query has no matches', function () {
- resource.cache = [{ id: 0, name: 'Jake' }, { id: 1, name: 'Jake' }];
-
- var cached = resource.find({ id: 3, name: 'Jake' });
- expect(cached).to.be.an('array');
- expect(cached.length).to.equal(0);
- });
- });
-
- describe('#aquire', function () {
- it('returns empty list if indices do not exist on cache', function () {
- resource.cache = [{ random: 'data' }];
-
- var indices = resource.aquire([1, 2]);
- expect(indices).to.be.an('array');
- expect(indices.length).to.equal(0);
- });
-
- it('returns empty list if cache is unavailable', function () {
- resource.cache = null;
-
- var indices = resource.aquire([1, 2]);
- expect(indices).to.be.an('array');
- expect(indices.length).to.equal(0);
- });
-
- it('returns cached objects in correspondence with indices', function () {
- resource.cache = [{ random: 'data' }];
-
- var indices = resource.aquire([0]);
- expect(indices).to.be.an('array');
- expect(indices.length).to.equal(1);
- expect(indices[0]).to.be.an('object');
- expect(indices[0]).to.have.property('random', 'data');
- });
- });
-
- describe('#GET', function () {
- it('cache proxy returns cached values if available', function () {
- var query = {more: 'stuff'}, i = 0;
- resource.cache = [{ random: 'data' }, {id: 2, more: 'stuff'}];
- resource.get = function () { i++; };
-
- resource._get(query, function (err, data) {
- expect(err).to.equal(null);
- expect(data).to.be.an('array');
- expect(data[0]).to.have.property('id', 2);
- expect(data[0]).to.have.property('more', 'stuff');
- expect(i).to.equal(0);
- });
- });
-
- it('cache proxy calls supplied GET if no cache', function (done) {
- resource.cache = null;
- resource.get = function (query, fn) {
- fn();
- };
-
- resource._get({id: 1}, done);
- });
- });
-
- describe('#POST', function () {
- it('will insert data in cache on succesful POST', function (done) {
- var i = 0;
-
- resource.cache = [{ random: 'data' }];
- resource.post = function (data, fn) { i++; fn(undefined, true); };
-
- resource._post({id: 2, more: 'stuff'}, function (err, result) {
- expect(resource.cache.length).to.equal(2);
- expect(resource.cache[1]).to.have.property('id', 2);
- expect(resource.cache[1]).to.have.property('more', 'stuff');
- expect(i).to.equal(1);
- expect(result).to.equal(true);
- done();
- });
- });
-
- it('will not cache values on failure', function (done) {
- resource.post = function (data, fn) {
- fn('POST errored somewhere');
- };
-
- resource._post({random: 'data'}, function (err, result) {
- // Error will be an instance of Error if POST is triggered by event.
- expect(err).to.be.equal('POST errored somewhere');
- expect(result).to.equal(false);
- done();
- });
- });
- });
-
- describe('#PUT', function () {
- it('will merge/update data in cache', function (done) {
- var i = 0;
-
- resource.cache = [{ random: 'data' }];
- resource.put = function (query, data, fn) { i++; fn(undefined, true); };
-
- resource._put({ random: 'data' }, {id: 2}, function (err, result) {
- expect(resource.cache.length).to.equal(1);
- expect(resource.cache[0]).to.have.property('id', 2);
- expect(resource.cache[0]).to.have.property('random', 'data');
- expect(i).to.equal(1);
- expect(result).to.equal(true);
- done();
- });
- });
-
- it('will append data if no indices where queried', function (done) {
- resource.cache = [{ random: 'data' }];
- resource.put = function (query, data, fn) { fn(undefined, true); };
-
- resource._put({ not: 'existing' }, {id: 2}, function (err, result) {
- expect(resource.cache.length).to.equal(2);
- expect(resource.cache[0]).to.have.property('random', 'data');
- expect(resource.cache[1]).to.have.property('id', 2);
- expect(result).to.equal(true);
- done();
- });
- });
-
-
- it('will not cache values on failure', function (done) {
- resource.put = function (query, data, fn) {
- fn('PUT errored');
- };
-
- resource._put({random: 'data'}, {}, function (err, result) {
- expect(err).to.be.equal('PUT errored');
- expect(result).to.equal(false);
- done();
- });
- });
- });
-
- describe('#DELETE', function () {
- it('removes indices from cache', function (done) {
- var i = 0;
-
- resource.cache = [{id: 1}, { random: 'data' }, {id: 2}];
- resource.delete = function (query, fn) { i++; fn(undefined, true); };
-
- resource._delete({random: 'data'}, function (err, result) {
- expect(result).to.equal(true);
- expect(resource.cache.length).to.equal(2);
- expect(resource.cache[1]).to.have.property('id', 2);
- expect(i).to.equal(1);
- done();
- });
- });
-
- it('returns true if nothing was deleted and if there are no errors', function (done) {
- resource.delete = function (query, fn) { fn(undefined, true); };
- resource.cache = [{id: 1}, { random: 'data' }, {id: 2}];
-
- resource._delete({not: 'in cache'}, function (err, result) {
- expect(result).to.equal(true);
- expect(resource.cache.length).to.equal(3);
- done();
- });
- });
- });
-
- describe('#proxyMethod', function () {
- it('returns a callable callback', function () {
- expect(resource.proxyMethod()).to.be.an('function');
- });
-
- it('checks for availability of developer supplied REST method', function (done) {
- var callback = resource.proxyMethod('post');
-
- callback(function (err, data) {
- expect(err).to.be.an.instanceof(Error);
- expect(err.message).to.equal('unable to call post on the resource');
- expect(data).to.equal(undefined);
- done();
- });
- });
- });
-
- describe('#proxy', function () {
- it('returns a callable callback', function () {
- expect(resource.proxy()).to.be.an('function');
- });
-
- it('ensures callback is deferred regardless of implementation', function (done) {
- var final = resource.proxy(done);
- final();
- });
-
- it('exposes supplied error and data', function (done) {
- var final = resource.proxy(function callback(err, data) {
- expect(data).to.be.an('object');
- expect(data).to.have.property('test', 1);
- expect(err).to.be.an.instanceof(Error);
- expect(err.message).to.be.equal('I errored');
- done();
- });
-
- final('I errored', { test: 1 });
- });
- });
-});

0 comments on commit 3180c8a

Please sign in to comment.