Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

controller

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

controller - npm Package Compare versions

Comparing version 0.6.2 to 1.0.0

388

lib/controller.js

@@ -1,60 +0,84 @@

var isRegExp = require('util').isRegExp;
var join = require('path').join;
var express = require('express');
var _ = require('underscore');
var methods = require('methods');
const isRegExp = require('util').isRegExp;
const join = require('path').join;
const Router = require('router');
const RouterLayer = require('router/lib/layer');
const _ = require('underscore');
const methods = require('methods');
const cuid = require('cuid');
const parseurl = require('parseurl');
var Controller = module.exports = function Controller() {
if (!(this instanceof Controller)) {
return new Controller();
}
const createAnonymousGroupName = () => `anonymous-middleware-group-${cuid()}`
var self = this;
this.routes = [],
this.actions = {},
this.middlewares = [],
this.chainCache = {},
this.app = express();
// Make controller look like a server
Object.defineProperty(this, 'handle', {
get: function() {
return this.app.handle.bind(this.app);
}
});
// Make sure Connect doesn't try to eat our route
Object.defineProperty(this, 'route', {
enumerable: true, configurable: true,
writable: false, value: this.createRoute
});
this._controllerInit = function controllerInit(req, res, next) {
var route = _.find(self.routes, function(route) {
return route.method === req.route.method
&& route.path === req.route.path
&& route.self === self;
module.exports = function createController() {
// an array of routes from URL paths or RegExps to actions.
// [ { method: 'get', path: '/user/:id', action: 'getUser', controller: 'controllerId' } ]
const routes = [];
// a map of actions indexed by their names
// { getUser: { groups: ['checkAuth', 'accessors'], handler: () => {} } }
const actions = {};
// an array of middlewares. each middleware is a function, with "controller" and
// "scope" properties (scope is an array of groups).
const middlewares = [];
// a cache of middleware chains so that a chain does not have to be calculated
// every time a route is called.
const chainCache = {};
const id = cuid();
const router = Router();
const Controller = (req, res, next) => {
router(req, res, next);
};
Controller.actions = actions;
Controller.routes = routes;
Controller.middlewares = middlewares;
Controller.router = Controller.app = router;
Controller._controllerId = id;
// middleware entry point, the inline middleware that is passed to router
const injectRouteScope = (req, res, next) => {
// find a route that matches this route
const pathname = parseurl(req).pathname;
const route = _.find(routes, function(route) {
var match = false;
try { match = route.layer.match(pathname) } catch (e) {}
return route.method === req.method.toLowerCase()
&& match
&& route.controller === id;
});
if (route) {
req.action = route.action;
req._action = self.actions[req.action];
// the metadata for the action, for example { groups: [], handler: ()=>{} }
const action = actions[route.action];
req.action = {
key: route.action,
groups: action.groups,
handler: action.handler
};
}
// create a scope ['all', ...scopes defined as a part of action..., 'actionName']
var scope = ['all'];
if (req._action) {
scope = scope.concat(req._action.groups);
scope.push(req.action);
if (req.action) {
scope = scope.concat(req.action.groups);
scope.push(req.action.key);
}
var key = req.route.path + '-' + scope.join(',');
if (!self.chainCache[key]) {
self.chainCache[key] = collectMiddlewares.call(self, scope);
req.route.callbacks = _.reject(req.route.callbacks, function(mw) {
return !!mw.scope && mw.self == self;
});
var chain = self.chainCache[key];
req.route.callbacks.splice.apply(req.route.callbacks,
[1, 0].concat(chain));
// check if the middleware chain has been calculated yet. If not, or if it's
// been invalidated, we calculate it again, clear the existing Controller
// middleware from the route's stack, and add it again.
const key = req.route.path + '-,' + scope.join(',');
if (!chainCache[key]) {
const chain = chainCache[key] = getMiddlewareMatchingScope(scope);
// first we remove any existing middleware
const stack = req.route.stack;
for (let i = 0; i < stack.length; ++i) {
const mw = stack[i].handle;
if (!mw.scope) continue;
stack.splice(i--, 1);
}
const layerChain = chain.map(mw => RouterLayer('/', {}, mw));
stack.splice.apply(stack, [1, 0].concat(layerChain));
}

@@ -64,156 +88,144 @@

};
}
const getMiddlewareMatchingScope = (scope) => {
const inScope = group => ~scope.indexOf(group);
return _.sortBy(
collectMiddlewaresMatchingScope(Controller, scope),
(mw) => scope.indexOf(_.find(mw.scope, inScope))
);
}
const collectMiddlewaresMatchingScope = (controller, scope, base = []) => {
if (controller.parent && controller.parent._controllerId) {
base = collectMiddlewaresMatchingScope(controller.parent, scope, base);
}
// Have run into production problems where controllers are created from different
// instances of the Controller library due to weird dependency installations -
// which means that we can't reference the `Controller` var and do `instanceof`,
// we have to set some kind of constant and check against it. At least, that
// seems like the most sensible way to me.
Object.defineProperty(Controller.prototype, '_type', {
enumerable: false, configurable: false, writable: false, value: '_controller_express_ext'
});
const inScope = (group) => ~scope.indexOf(group);
return base.concat(controller.middlewares.filter(mw => mw.scope.some(inScope)));
}
const addSubController = function (route, controller) {
if (controller._controllerId) controller.parent = Controller;
router.use(route, controller);
return Controller;
};
Controller.middleware = Controller.use = function (route, controller) {
// just assume we're mounting a subcontroller/app to start with...
if (typeof route != 'string' && !isRegExp(route)) {
controller = route;
route = '/';
}
if (controller && controller._controllerId) return addSubController(route, controller);
else if (route == '/') { route = controller; controller = undefined }
// oh, not a controller? ok, proceed as normal...
const args = _.flatten([].slice.call(arguments), true);
const scope = [];
const fns = [];
function collectMiddlewares(scope) {
var inScope = function(group) { return ~scope.indexOf(group); };
return _.sortBy(
_collectMiddlewares.call(this, scope),
function(mw) { return scope.indexOf(_.find(mw.scope, inScope)) }
);
}
while (args.length) {
const arg = args.shift();
if (arg == null) continue;
(typeof arg === 'function' ? fns : scope).push(arg);
}
function _collectMiddlewares(scope, base) {
if (!base) base = [];
if (this.parent && this.parent._type && this.parent._type == this._type)
base = _collectMiddlewares.call(this.parent, scope, base);
if (!scope.length) scope.push('all');
fns.forEach(function(fn) {
fn.scope = scope;
fn.controller = id;
middlewares.push(fn);
});
var inScope = function(group) { return ~scope.indexOf(group); };
return base.concat(
_.chain(this.middlewares)
.filter(function(mw) { return mw.scope.some(inScope) })
.value()
);
}
// Purge any related caches.
Object.keys(chainCache).forEach(function(chain) {
var groups = chain.split(',');
if (!!groups.some(group => ~scope.indexOf(group))) {
delete chainCache[chain];
}
});
Controller.prototype.addSubController = function(route, controller) {
if (typeof route != 'string') {
controller = route;
route = '/';
return Controller;
}
Controller.route = function (method, path, action) {
method = method.toLowerCase();
router[method](path, injectRouteScope, function(req, res, next) {
if (!actions[action]) {
next(new Error('Unhandled action - ' + method + ' ' + action));
} else {      
actions[action].handler.call(Controller, req, res, next);
}
});
routes.push({
method,
path,
action,
layer: router.stack[router.stack.length - 1],
controller: id
});
if (controller instanceof Controller) controller.parent = this;
this.app.use(route, controller);
};
Controller.prototype.middleware = Controller.prototype.use =
function middleware(route, controller) {
// just assume we're mounting a subcontroller/app to start with...
if (typeof route != 'string' && route.handle) {
controller = route;
route = '/';
return Controller;
}
if (controller && controller.handle)
return this.addSubController(route, controller);
// oh, not a controller/app? ok, proceed as normal...
var args = _.flatten([].slice.call(arguments), true),
scope = [], fns = [], self = this;
while (args.length) {
var arg = args.shift();
(typeof arg === 'function' ? fns : scope).push(arg);
}
if (!scope.length) scope.push('all');
var isAll = !!~scope.indexOf('all');
fns.forEach(function(fn) {
fn.scope = scope;
fn.self = self;
self.middlewares.push(fn);
});
// Purge any related caches.
Object.keys(this.chainCache).forEach(function(chain) {
var groups = chain.split(',');
if (!!groups.some(function(group) { return ~scope.indexOf(group) })) {
delete self.chainCache[chain];
Controller.define = function (name, groups, handler) {
if (typeof groups == 'function') handler = groups, groups = [];
if (Array.isArray(groups)) {
//clear old anonymous middlewares if we're overwriting
if (actions[name]) {
for (let i = 0; i < middlewares.length; ++i) {
if (!middlewares[i].anonymous || middlewares[i].scope[0] != name) continue;
middlewares.splice(i--, 1);
}
}
groups = groups.filter((group) => {
if (typeof group == "function") {
group.anonymous = true;
Controller.use(name, group);
return false;
} else {
return true;
}
})
}
});
return this;
}
actions[name] = { groups, handler };
Controller.prototype.createRoute = function route(method, path, action) {
method = method.toLowerCase();
var self = this;
this.routes.push({ method: method, path: path, action: action, self: self});
this.app[method](path, this._controllerInit, function(req, res, next) {
if (!self.actions[action]) {
next(new Error('Unhandled action - ' + method + ' ' + action));
} else {
self.actions[action].handler.call(self, req, res, next);
}
return Controller;
}
methods.forEach(function(method) {
Controller[method] = function() {
return Controller.route.apply(null, [method].concat([].slice.call(arguments)));
};
});
return this;
}
Controller.prototype.define = function define(name, groups, handler) {
if (typeof groups == 'function') handler = groups, groups = [];
if (Array.isArray(groups)) {
var self = this;
//clear old anonymous middlewares if we're overwriting
if (this.actions[name]) {
self.middlewares.forEach(function(middleware) {
if (middleware.anonymous && middleware.scope[0] == name)
self.middlewares = _.without(self.middlewares, middleware);
});
}
groups = groups.filter(function(group) {
if (typeof group == "function") {
group.anonymous = true;
self.use(name, group);
return false;
Controller.direct = function (method, path /* [mw/g], fn */) {
const args = [].slice.call(arguments);
const groups = [];
const id = createAnonymousGroupName();
args.shift(); args.shift();
const handler = args.pop();
var item;
while (args.length) {
item = args.shift();
if (typeof item === 'string') {
groups.push(item);
} else {
return true;
const anonGroup = createAnonymousGroupName();
groups.push(anonGroup);
Controller.use(anonGroup, item);
}
})
}
}
Controller.define(id, groups, handler);
Controller.route(method, path, id);
this.actions[name] = { groups: groups, handler: handler };
return this;
}
methods.forEach(function(method) {
Controller.prototype[method] = function() {
return this.route.apply(this, [method].concat([].slice.call(arguments)));
};
});
Controller.prototype.direct = function(method, path /* [mw/g], fn */) {
var args = [].slice.call(arguments),
groups = [], item, id = createAnonymousGroupName();
args.shift(); args.shift();
var handler = args.pop();
while (args.length) {
item = args.shift();
if (typeof item === 'string') {
groups.push(item);
} else {
var anonGroup = createAnonymousGroupName();
groups.push(anonGroup);
this.middleware(anonGroup, item);
}
return Controller;
}
this.define(id, groups, handler);
this.route(method, path, id);
return this;
return Controller;
}
var createAnonymousGroupName = (function() {
var anonSeed = Math.floor(Math.random() * Math.pow(10, 10));
return function() { return 'anonymous-middleware-group-' + ++anonSeed }
})();
{
"name": "controller",
"version": "0.6.2",
"version": "1.0.0",
"description": "an action controller for express",

@@ -14,10 +14,13 @@ "main": "lib/controller.js",

"devDependencies": {
"supertest": "~0.1.2",
"mocha": "*"
"express": "^4.16.2",
"mocha": "^4.0.1",
"supertest": "^3.0.0"
},
"dependencies": {
"express": "~3.4.4",
"underscore": "~1.3.3",
"methods": "0.0.1"
"cuid": "^1.3.8",
"methods": "^1.1.2",
"parseurl": "^1.3.2",
"router": "^1.3.2",
"underscore": "^1.8.3"
}
}

@@ -76,3 +76,3 @@ var assert = require('assert');

c0.app.use('/second/', c);
c0.use('/second/', c);
app.use('/first/', c0);

@@ -79,0 +79,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc