Skip to content

Commit

Permalink
Added implementation of everymodule to match newer API. Added in Prom…
Browse files Browse the repository at this point in the history
…ise and Sequence constructors used in the implementation of the new API.
  • Loading branch information
bnoguchi committed Apr 5, 2011
1 parent d9370dc commit 32b8fd8
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 29 deletions.
21 changes: 21 additions & 0 deletions lib/deprecated.js
Expand Up @@ -68,6 +68,27 @@ everyauth(
)
);

var fb = module.exports =
oauthModule.submodule('facebook')
// TODO submodule should
// set fb.name = 'facebook'
.apiHost('https://graph.facebook.com')
.setters('scope')
.routeStep('authRequest')
.uri('/auth/facebook')
.step('authRequest')
.accepts('req res abc')
.returns('*')
.get('/auth/facebook')
// Should be able to access module
// properties from within a step
// definition
.step('authCallback')
.get('/auth/facebook/callback')
.step('addToSession')
.define( function (sess, auth) {

})

/** OAuth **/

Expand Down
149 changes: 149 additions & 0 deletions lib/everymodule.alt.js
@@ -0,0 +1,149 @@
var OAuth = require('oauth').OAuth2
, url = require('url')
, MaterializedSequence = require('./sequence')
, clone = require('./utils').clone;

function route (method) {
return function (alias) {
this._routes[method][alias];
this.configurable(alias);
this._currentRoute = method + '.' + alias;
return this;
};
}

// TODO Add in check for missing step definitions
// from the set of step declarations
var everyModule = module.exports = {
definit: function (fn) {
// Remove any prior `init` that was assigned
// directly to the object via definit and not
// assigned via prototypal inheritance
if (this.hasOwnProperty('init'))
delete this.init;

var super = this.init;
// since this.hasOwnProperty('init') is false

this.init = function () {
this.super = super;
fn.apply(this, arguments);
delete this.super;

// Do module compilation here, too
};
}
, get: route('get')
, post: route('post')
, configurable: function (property) {
if (property.indexOf(' ') !== -1) {
// e.g., property === 'apiHost appId appSecret'
var self = this;
property.split(/\s+/).forEach( function (_property) {
self.configurable(_property);
});
return this;
}

// Else we have a single property name
if (!this[property])
this[property] = function (setTo) {
var k = '_' + property;
if (!arguments.length) return this[k];
this[k] = setTo;
return this;
};
return this;
}
, step: function (name, opts) {
// TODO Use opts
var steps = this._steps;
if (!steps[name]) {
steps[name] = {};
steps._order.push(name);

// For configuring what the actual business
// logic is:
// fb.step('fetchOAuthUser') generates the method
// fb.fetchOAuthUser that can be used like
// fb.fetchOAuthUser( function (...) {
// // Business logic goes here
// } );
this.configurable(name);
}
this._currentStep = steps[name];
return this;
}
, accepts: function (input) {
var step = this._currentStep;
step.accepts = input
? input.split(/\s+/)
: null;
return this;
}
, promises: function (output) {
var step = this._currentStep;
step.promises = output
? output.split(/\s+/)
: null;
return this;
}

/**
* Creates a new submodule using prototypal
* inheritance
*/
, submodule: function (name) {
var submodule = Object.create(this)
, self = this;
['_routes', '_steps'].forEach(
function (toClone) {
submodule[toClone] = clone(self[toClone]);
}
);
submodule.name = name;
return submodule;
}

/**
* Decorates the app with the routes required of the
* module
*/
, routeApp: function (app) {
for (var method in this._routes) {
for (var routeAlias in this._routes[method]) {
app[method](this[routeAlias](), this.routeHandler(method, routeAlias));
}
}
}

// Returns the route handler
// This is also where a lot of the magic happens
, routeHandler: function (method, routeAlias) {
var stepsHash = this._steps[method + '.' + routeAlias]
// Move orderedSteps to a getter?
, orderedSteps = stepsHash._order.map( function (stepName) {
return stepsHash[stepName];
})
, seq = new MaterializedSequence(orderedSteps);

return seq.routeHandler();
// This kicks off a sequence of steps based on a
// route
// Creates a new chain of promises and exposes the leading promise
// to the incoming (req, res) pair from the route handler
}

// _order keeps the order of the steps by name
// _steps maps method.routes to step names to step objects
// and an ordering of the step names ('_order')
// A step object is { accepts: [...], promises: [...] }
, _steps: {}
, _routes: {
get: {}
, post: {}
}
}.configurable('init');

everyModule
.step('findOrCreateUser');
24 changes: 1 addition & 23 deletions lib/facebook.alt.js
Expand Up @@ -12,26 +12,4 @@ oauthModule.submodule('facebook')
var oauthUser = JSON.parse(data);
p.fulfill(oauthUser);
})
})

var fb = module.exports =
oauthModule.submodule('facebook')
// TODO submodule should
// set fb.name = 'facebook'
.apiHost('https://graph.facebook.com')
.setters('scope')
.routeStep('authRequest')
.uri('/auth/facebook')
.step('authRequest')
.accepts('req res abc')
.returns('*')
.get('/auth/facebook')
// Should be able to access module
// properties from within a step
// definition
.step('authCallback')
.get('/auth/facebook/callback')
.step('addToSession')
.define( function (sess, auth) {

})
});
15 changes: 9 additions & 6 deletions lib/oauth.alt.js
Expand Up @@ -8,7 +8,7 @@ var everyModule = require('./everymodule')

var oauth = module.exports =
everyModule.submodule('oauth')
.init( function () {
.definit( function () {
this.oauth = new OAuth(this.appId(), this.appSecret(), this.apiHost());
})
.configurable('apiHost appId appSecret myHostname redirectPath')
Expand All @@ -28,15 +28,18 @@ everyModule.submodule('oauth')
.promises('accessToken refreshToken')
.step('fetchOAuthUser')
.accepts('accessToken refreshToken')
.promises('user')
.step('compile')
.accepts('accessToken refreshToken user')
.promises('auth')
.promises('oauthUser')
.step('getSession')
.accepts('req')
.promises('session')
.step('findOrCreateUser')
.accepts('session accessToken oauthUser')
.promises('user')
.step('compile')
.accepts('accessToken refreshToken oauthUser user')
.promises('auth')
.step('addToSession')
.accepts('session')
.accepts('session auth')
.promises(null)
.step('sendResponse')
.accepts('res')
Expand Down
40 changes: 40 additions & 0 deletions lib/promise.js
@@ -0,0 +1,40 @@
var Promise = module.exports = function (values) {
this._callbacks = [];
this._errbacks = [];
if (arguments.length > 0) {
this.fulfill.apply(this, values);
}
};

Promise.prototype = {
callback: function (fn, scope) {
if (this.values) {
return fn.apply(scope, this.values);
}
this._callback.push([fn, scope]);
return this;
}
, errback: function (fn, scope) {
if (this.err) {
return fn.call(scope, this.err);
}
this._errbacks.push([fn, scope]);
return this;
}
, fulfill: function () {
var callbacks = this._callbacks;
this.values = arguments;
for (var i = 0, l = callbacks.length; i < l; i++) {
callbacks[i][0].apply(callbacks[i][1], arguments);
}
return this;
}
, fail: function (err) {
var errbacks = this._errbacks;
this.err = err;
for (var i = 0, l = errbacks.length; i < l; i++) {
errbacks[i][0].call(errbacks[i][1], err);
}
return this;
}
};
100 changes: 100 additions & 0 deletions lib/sequence.js
@@ -0,0 +1,100 @@
var Promise = require('./promise');

function MaterializedSequence (steps) {
// Our sequence state is encapsulated in seq.values
this.values = {};
this.steps = steps;
}

MaterializedSequence.prototype = {
routeHandler: function () {
var seq = this
, steps = this.steps
, firstStep = steps[0];

var promise;
for (var i = 0, l = steps.length, step; i < l; i++) {
step = steps[i];
}
var promises = this.steps.map( function (step) {
var accepts = step.accepts
, promises = step.promises
, block = step.fn;
});


// This kicks off a sequence of steps based on a
// route
// Creates a new chain of promises and exposes the leading promise
// to the incoming (req, res) pair from the route handler
return function () {
var args = Array.prototype.slice.call(arguments, 0);
firstStep.accepts.forEach( function (valName, i) {
// Map the incoming arguments to the named parameters
// that the first step is expected to accept.
seq.values[valName] = args[i];
});
seq.start();

};
}
/**
* @param {Object} step
* @returns {Promise}
*/
, applyStep: function (step) {
var accepts = step.accepts;
, promises = step.promises;
, block = step.block;
// Unwrap values based on step.accepts
var args = accepts.reduce( function (args, accept) {
args.push(seq.values[accept]);
return args;
}, []);
// Apply the step logic
// TODO Set this.module
ret = block.apply(this.module, args);

// Convert return value into a Promise
// if it's not yet a Promise
if (! (ret instanceof Promise)) {
if (Array.isArray(ret))
ret = new Promise(ret);
else
ret = new Promise([ret]);
}

var seq = this;
ret.callback( function () {
// Store the returned values
// in the sequence's state via seq.values
var returned = arguments
, vals = seq.values;
promises.forEach( function (valName, i) {
vals[valName] = returned[i];
});
});

return ret;
}
, _bind: function (priorPromise, step) {
var nextPromise = new Promise()
, seq = this;
priorPromise.callback( function () {
seq.applyStep(step).callback( function () {
nextPromise.fulfill();
});
});
return nextPromise;
}
, start: function () {
var steps = this.steps;

// Pipe through the steps
var priorStepPromise = this.applyStep(steps[0]);
for (var i = 1, l = steps.length; i < l; i++) {
priorStepPromise = bind(priorStepPromise, steps[i]);
}
return priorStepPromise;
}
};

0 comments on commit 32b8fd8

Please sign in to comment.