Permalink
Browse files

Added implementation of everymodule to match newer API. Added in Prom…

…ise and Sequence constructors used in the implementation of the new API.
  • Loading branch information...
1 parent d9370dc commit 32b8fd86a0ba15a1fbca78828be5ed4750801d96 @bnoguchi committed Apr 5, 2011
Showing with 320 additions and 29 deletions.
  1. +21 −0 lib/deprecated.js
  2. +149 −0 lib/everymodule.alt.js
  3. +1 −23 lib/facebook.alt.js
  4. +9 −6 lib/oauth.alt.js
  5. +40 −0 lib/promise.js
  6. +100 −0 lib/sequence.js
View
@@ -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 **/
View
@@ -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');
View
@@ -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) {
-
- })
+ });
View
@@ -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')
@@ -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')
View
@@ -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;
+ }
+};
View
@@ -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.