flowstate
Advanced tools
Comparing version 0.3.2 to 0.4.0
exports.Manager = require('./manager'); | ||
exports.SessionStore = require('./stores/session'); | ||
exports.load = require('./middleware/load'); | ||
exports.resume = require('./middleware/resume'); | ||
exports.resumeError = require('./middleware/resumeError'); | ||
exports.clean = require('./middleware/clean'); | ||
exports.middleware = {}; | ||
exports.middleware.load = require('./middleware/load'); | ||
exports.middleware.complete = require('./middleware/complete'); | ||
exports.middleware.completeError = require('./middleware/completeError'); | ||
exports.middleware.clean = require('./middleware/clean'); | ||
exports.ExpiredStateError = require('./errors/expiredstateerror'); | ||
exports.MissingStateError = require('./errors/missingstateerror'); |
@@ -5,7 +5,9 @@ var flatten = require('utils-flatten'); | ||
function Manager() { | ||
function Manager(store) { | ||
this._flows = {}; | ||
this._stt = {}; | ||
this._store = store; | ||
} | ||
Manager.prototype.add = function(name, begin, resume) { | ||
Manager.prototype.use = function(name, begin, resume) { | ||
begin = begin && flatten(Array.prototype.slice.call(begin, 0)); | ||
@@ -20,3 +22,19 @@ resume = resume && flatten(Array.prototype.slice.call(resume, 0)); | ||
Manager.prototype.goto = function(name, req, res, next) { | ||
Manager.prototype.transition = function(name, from, trans) { | ||
if (!Array.isArray(trans)) { | ||
trans = [ trans ]; | ||
} | ||
trans = trans && flatten(Array.prototype.slice.call(trans, 0)); | ||
this._stt[name + '|' + from] = trans; | ||
} | ||
Manager.prototype.goto = function(name, options, req, res, next) { | ||
if (typeof next !== 'function') { | ||
next = res; | ||
res = req; | ||
req = options; | ||
options = undefined; | ||
} | ||
var flow = this._flows[name]; | ||
@@ -26,9 +44,26 @@ if (!flow) { throw new Error("Cannot find flow '" + name + "'"); } | ||
if (options) { | ||
req.locals = options; | ||
} | ||
dispatch(flow.begin)(null, req, res, next); | ||
} | ||
Manager.prototype.loadState = function(options) { | ||
return require('./middleware/load')(this._store, options); | ||
}; | ||
Manager.prototype.complete = function(options) { | ||
return require('./middleware/complete')(this, this._store, options); | ||
}; | ||
Manager.prototype.completeError = | ||
Manager.prototype.completeErrorHandler = | ||
Manager.prototype.completeWithError = function(options) { | ||
return require('./middleware/completeError')(this, this._store, options); | ||
}; | ||
Manager.prototype._resume = function(name, err, req, res, next) { | ||
var flow = this._flows[name]; | ||
if (!flow) { throw new Error("Cannot find flow '" + name + "'"); } | ||
if (!flow.resume) { throw new Error("Cannot resume flow '" + name + "'"); } | ||
if (!flow) { return next(new Error("Cannot find flow '" + name + "'")); } | ||
if (!flow.resume) { return next(new Error("Cannot resume flow '" + name + "'")); } | ||
@@ -38,3 +73,10 @@ dispatch(flow.resume)(err, req, res, next); | ||
Manager.prototype._transition = function(name, from, err, req, res, next) { | ||
var trans = this._stt[name + '|' + from]; | ||
if (!trans) { return next(err); } | ||
dispatch(trans)(err, req, res, next); | ||
} | ||
module.exports = Manager; |
@@ -0,7 +1,34 @@ | ||
/** | ||
* Module dependencies. | ||
*/ | ||
var SessionStore = require('../stores/session'); | ||
/** | ||
* Load state. | ||
* | ||
* This middleware is used to load state associated with a request. The state | ||
* will be made available at `req.state`. | ||
* | ||
* HTTP is a stateless protocol. In order to support stateful interactions, the | ||
* client and server need to operate in coordination. The server is responsible | ||
* for persisting state associated with a request. The client is responsible for | ||
* indicating that state in subsequent requests to the server (via a `state` | ||
* parameter in the query or body, by default). The server then loads the | ||
* previously persisted state in order to continue processing the transaction. | ||
* | ||
* Options: | ||
* | ||
* name set req.state only if the state name is equal to the option value | ||
* | ||
* @return {Function} | ||
* @api public | ||
*/ | ||
module.exports = function(store, options) { | ||
if (typeof options == 'string') { | ||
options = { name: options }; | ||
} | ||
options = options || {}; | ||
var name = options.name; | ||
var getHandle = options.getHandle || function(req) { | ||
@@ -11,10 +38,26 @@ return (req.query && req.query.state) || (req.body && req.body.state); | ||
return function loadState(req, res, next) { | ||
var h = getHandle(req); | ||
if (!h) { return next(); } | ||
if (!h) { | ||
if (options.required) { | ||
return next(new Error("Failed to load required state '" + name + "'")); | ||
} | ||
return next(); | ||
} | ||
store.load(req, h, function (err, state) { | ||
if (err) { return next(err); } | ||
req.state = state; | ||
if (!name) { | ||
req.state = state; | ||
} else if (name == state.name) { | ||
req.state = state; | ||
} else { | ||
// The loaded state is not the expected state. Save what was loaded, | ||
// as an optimization to avoid re-loading later if the state is resumed. | ||
req._state = state; | ||
} | ||
if (options.required && !req.state) { | ||
return next(new Error("Failed to load required state '" + name + "'")); | ||
} | ||
next(); | ||
@@ -21,0 +64,0 @@ }); |
@@ -58,11 +58,13 @@ var uid = require('uid-safe').sync; | ||
SessionStore.prototype.save = function(req, state, cb) { | ||
state.initiatedAt = Date.now(); | ||
if (req.state && req.state.handle) { | ||
state.prev = req.state.handle; | ||
SessionStore.prototype.save = function(req, state, options, cb) { | ||
if (typeof options == 'function') { | ||
cb = options; | ||
options = undefined; | ||
} | ||
options = options || {}; | ||
state.initiatedAt = state.initiatedAt || Date.now(); | ||
var key = this._key; | ||
var h = uid(24); | ||
var h = options.h || uid(8); | ||
req.session[key] = req.session[key] || {}; | ||
@@ -75,6 +77,3 @@ req.session[key][h] = clone(state); | ||
SessionStore.prototype.update = function(req, h, state, cb) { | ||
if (req.state && req.state.handle == h) { | ||
state.initiatedAt = req.state.initiatedAt; | ||
if (req.state.prev) { state.prev = req.state.prev; } | ||
} | ||
if (state.handle === h) { delete state.handle; } | ||
@@ -85,3 +84,3 @@ var key = this._key; | ||
return cb(null); | ||
return cb(null, h); | ||
} | ||
@@ -88,0 +87,0 @@ |
{ | ||
"name": "flowstate", | ||
"version": "0.3.2", | ||
"version": "0.4.0", | ||
"description": "Stateful, transactional flows using page-based navigation.", | ||
@@ -28,3 +28,5 @@ "author": { | ||
"mocha": "^2.0.0", | ||
"chai": "^3.0.0" | ||
"chai": "^3.0.0", | ||
"sinon-chai": "^2.8.0", | ||
"chai-connect-middleware": "0.3.x" | ||
}, | ||
@@ -31,0 +33,0 @@ "scripts": { |
18625
468
5