Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
6 changed files
with
320 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
}; |