spirit-router
Version:
fast router for spirit
221 lines (195 loc) • 7.08 kB
JavaScript
;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var Promise = require("bluebird");
var routes = require("./routes");
var spirit = require("spirit");
/**
* sees if there's a match with route `compiled_route` based on
* the incoming requests method and path
*
* @param {Route} route - a Route (from routes.compile)
* @param {string} req_method - the incoming request method
* @param {string} req_path - the incoming request's url
* @return {array}
*/
var _lookup = function _lookup(route, req_method, req_path) {
if (route.method === req_method || route.method === "*") {
var params = route.path.re.exec(req_path);
if (params) {
return params;
}
}
};
/**
* Wraps `Route` with a routing function
* If the match fails, it simply returns undefined
* If it succeeds, it will call and return Route's handler
*
* @param {Route} Route - a Route
* @return {function} router that wraps over a Route
*/
var wrap_router = function wrap_router(Route) {
return function (request, prefix) {
if (typeof prefix !== "string") prefix = "";
var url = request.url.substring(prefix.length);
var params = _lookup(Route, request.method, url);
if (!params) {
return undefined;
}
request.params = routes.decompile(Route, params);
return Route.handler(request);
};
};
var wrap_context_router = function wrap_context_router(handler, name) {
return function (request, prefix) {
if (typeof prefix !== "string") prefix = "";
prefix = prefix + name;
if (request.url.indexOf(prefix) === 0) {
return handler(request, prefix);
}
};
};
/**
* Iterate over an array of wrapped Routes
* Stopping when a result is returned by a Route
*
* @param {array} arr - an array of wrapped Routes
* @return {Promise} a promise of the result of a Route
*/
var reduce_r = function reduce_r(arr) {
return function (request, prefix) {
// NOTE
//if (arr.length === 1) return arr[0](request, prefix)
var p = Promise.resolve();
var _loop = function _loop(i) {
p = p.then(function (v) {
if (typeof v !== "undefined") {
return v; // if there is a value, stop iterating
}
return arr[i](request, prefix);
});
};
for (var i = 0; i < arr.length; i++) {
_loop(i);
}
return p;
};
};
/**
* Returns a spirit handler
*
* The handler reduces over `arr_routes`, compiling
* each element in `arr_routes` to be a Route.
* And wrapping each Route with a spirit handler
* that acts as a router
*
* The `named` paramter is used for routing
* It is matched against the prefix of a URL
*
* @public
* @param {string} named - optional prefix to match
* @param {*} routes - a route, or array of routes or compiled route(s)
* @return {function} wrapped_route_handler
*/
var define = function define(named, arr_routes) {
if (typeof arr_routes === "undefined") {
arr_routes = named;
named = "";
}
if (!Array.isArray(arr_routes)) {
throw new TypeError("Expected `define` to be called with an array (of routes)");
}
arr_routes = arr_routes.slice();
var compile_routes = arr_routes.map(function (_route) {
if (typeof _route === "function") return _route;
return wrap_router(routes.compile.apply(undefined, _route));
});
var handler = reduce_r(compile_routes);
var router_and_handle = wrap_context_router(handler, named);
return function (request, prefix, handler_only) {
if (handler_only === true) return [handler, named];
return router_and_handle(request, prefix);
};
};
/**
* wraps a Route or a routing function
* to have middleware associated with it
*
* @public
* @param {Route|function} route - either a Route or a routing function (returned by define)
* @param {array} middleware - an array of middleware functions
* @return {function} the original `route` wrapped with middleware
*/
var wrap = function wrap(route, middleware) {
if (!Array.isArray(middleware)) {
if (typeof middleware !== "function") {
throw new TypeError("Expected `wrap` to be called with a middleware(function) or an array of middleware");
}
middleware = [middleware];
} else {
middleware = middleware.slice();
}
// wrapping a route
if (typeof route !== "function") {
var Route = routes.compile.apply(undefined, route);
Route.handler = spirit.compose(Route.handler, middleware);
return wrap_router(Route);
}
// otherwise wrapping a function (from define)
var err_msg = "Unable to apply middlewares to route, route being passed to `wrap` does not take middlewares.";
if (route.length !== 3) throw new Error(err_msg);
var r = route(undefined, undefined, true);
if (typeof r[0] !== "function" || typeof r[1] !== "string") throw new Error(err_msg);
return wrap_context_router(compose_args(r[0], middleware), r[1], "_routing");
};
/**
* Like `spirit.compose` but injects a final middleware
* and wraps the initial function
*
* The initial function will save it's arguments on the request
* The final injected middleware will clean up the initial func
* And call `handler` with the initial arguments to the
* initial function
*
* The main purpose of this is to maintain handler (router)
* arguments when the handler is wrapped with middleware
*
* @param {function} handler - final handler function
* @param {array} middleware - an array of middleware functions
* @param {string} prop_name - property name to set on the first argument (request), defaults to "_tmp"
* @return {function} function that calls middleware and handler
*/
var compose_args = function compose_args(handler, middleware, prop_name) {
prop_name = prop_name || "_tmp";
var cleanup = function cleanup(handler) {
return function (obj) {
if ((typeof obj === "undefined" ? "undefined" : _typeof(obj)) !== "object" || obj[prop_name] === undefined) {
throw new TypeError("(compose_args) The first argument was changed unexpectedly, (ex: request doesn't exist or has been structurally altered in an unrecoverable way)");
}
// clean up and inject
var args = obj[prop_name];
delete obj[prop_name];
args.unshift(obj);
return handler.apply(undefined, args);
};
};
middleware.push(cleanup);
var fn = spirit.compose(handler, middleware);
return function (obj) {
if ((typeof obj === "undefined" ? "undefined" : _typeof(obj)) !== "object") {
throw TypeError("(compose_args) Expected first argument to be an object (ex: request)");
}
// init
var args = Array.prototype.slice.call(arguments);
obj[prop_name] = args.slice(1);
return fn.apply(undefined, args);
};
};
module.exports = {
_lookup: _lookup,
define: define, // public
reduce_r: reduce_r,
wrap: wrap, // public
wrap_router: wrap_router,
compose_args: compose_args
};