-
Notifications
You must be signed in to change notification settings - Fork 447
/
everymodule.js
159 lines (146 loc) · 4.72 KB
/
everymodule.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
var OAuth = require('oauth').OAuth2
, url = require('url')
, MaterializedSequence = require('./sequence')
, clone = require('./utils').clone;
function route (method) {
return function (alias) {
this._routes[method].push(alias);
this.configurable(alias);
this._currentRoute = method + '.' + alias;
return this;
};
}
// TODO Add in check for missing step definitions
// from the set of step declarations
// TODO Add introspection to list incompletely defined steps
// TODO Add in ability for step to be optional
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
};
return this;
}
, 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 (e.g., for ordering information {before: 'otherStepName'})
var steps = this._steps
, routeSteps = steps[this._currentRoute];
if (!routeSteps) {
routeSteps = steps[this._currentRoute] = {_order: []};
}
routeSteps._order.push(name);
if (!routeSteps[name]) routeSteps[name] = {name: 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 = routeSteps[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) {
this.init();
var self = this;
for (var method in this._routes) {
this._routes[method].forEach( function (routeAlias) {
var path = self[routeAlias]();
if (!path)
throw new Error('You have not defined a path for the route alias ' + routeAlias + '.');
app[method](path, self.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(this, 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
}
// _steps maps method.routes to step names to step objects
// and an ordering of the step names (via `_order` key
// which keeps the ordered list of step names)
// A step object is { accepts: [...], promises: [...] }
, _steps: {}
, _routes: {
get: []
, post: []
}
}.configurable('init');
everyModule
.step('findOrCreateUser');