Comparing version 0.0.2 to 0.1.1
'use strict'; | ||
const koa = require('koa'); | ||
const co = require('co'); | ||
const _ = require('lodash'); | ||
const delegate = require('delegates'); | ||
const http = require('http'); | ||
const loader = require('./loader'); | ||
const Router = require('./lib/router'); | ||
const compose = require('koa-compose'); | ||
const filter = require('filter-match').filter; | ||
const Router = require('./lib/router/router'); | ||
const resolver = require('resolve-keypath').resolver; | ||
const requireDir = require('require-dir'); | ||
const onFinished = require('on-finished'); | ||
const parse = require('parseurl'); | ||
const exceptions = require('./exceptions'); | ||
const statuses = require('statuses'); | ||
const path = require('path') | ||
const base = path.dirname(module.parent.filename); | ||
const path = require('path'); | ||
@@ -18,23 +23,157 @@ class BayApplication { | ||
} | ||
this.app = koa(); | ||
this.base = options.base || base; | ||
this.env = process.env.NODE_ENV || 'development'; | ||
this.subdomainOffset = 2; | ||
this.middleware = []; | ||
this.getVersion = options.getVersion; | ||
this.proxy = options.proxy; | ||
this.base = options.base; | ||
if (!this.base) { | ||
throw new Error('missing base path'); | ||
} | ||
this.router = new Router(); | ||
_.assign(this.app, options); | ||
delegate(this, 'app') | ||
.method('use') | ||
.method('callback') | ||
.method('inspect') | ||
.method('toJSON') | ||
this.getController = resolver(requireDir(path.join(this.base, 'controllers'), { recurse: true }), '/'); | ||
this.getVersionTransformer = resolver(requireDir(path.join(this.base, 'versions'), { recurse: true }), '/'); | ||
} | ||
/** | ||
* Use the given middleware `fn`. | ||
* | ||
* @param {GeneratorFunction} fn | ||
* @return {Application} self | ||
* @api public | ||
*/ | ||
use(fn) { | ||
this.middleware.push(fn); | ||
return this; | ||
} | ||
listen() { | ||
const loaders = loader(this.base); | ||
this.app.use(loaders.router(this.router, loaders.controller)); | ||
const server = http.createServer(this.app.callback()); | ||
const server = http.createServer(this.callback()); | ||
return server.listen.apply(server, arguments); | ||
} | ||
/** | ||
* Return JSON representation. | ||
* We only bother showing settings. | ||
* | ||
* @return {Object} | ||
* @api public | ||
*/ | ||
toJSON() { | ||
return _.pick(this, ['subdomainOffset', 'proxy', 'env']); | ||
} | ||
callback() { | ||
const self = this; | ||
return function (req, res) { | ||
res.statusCode = 404; | ||
// Find the matching route | ||
let match | ||
try { | ||
match = self.router.match(parse(req).pathname, req.method); | ||
} catch (err) { | ||
return self.handleError(req, res, err); | ||
} | ||
if (!match) { | ||
return self.handleError(req, res, new exceptions.RoutingError('No route matches')); | ||
} | ||
// Resolve the controller | ||
const actionName = match.handler.action; | ||
const controllerName = match.handler.controller; | ||
const ControllerClass = self.getController(controllerName); | ||
if (!ControllerClass) { | ||
return self.handleError(req, res, new exceptions.RoutingError(`Controller ${controllerName} not found`)); | ||
} | ||
if (!ControllerClass.prototype[actionName]) { | ||
return self.handleError(req, res, | ||
new exceptions.RoutingError(`Action ${controllerName}#${actionName} not found`)); | ||
} | ||
onFinished(res, function (err) { | ||
if (err) { | ||
self.handleError(req, res, convertToError(err)); | ||
} | ||
}); | ||
const controller = new ControllerClass(self, req, res); | ||
controller.route = match; | ||
controller.params = match.params; | ||
const middlewares = self.middleware.slice(); | ||
if (self.getVersion) { | ||
const version = self.getVersion(controller); | ||
const versionTransformer = self.getVersionTransformer(`${version}/${controllerName}`); | ||
if (versionTransformer && versionTransformer[actionName]) { | ||
middlewares.push(versionTransformer[actionName]); | ||
} | ||
} | ||
if (controller._middleware) { | ||
filter(actionName, controller._middleware).forEach(v => { | ||
middlewares.push(typeof v.name === 'function' ? v.name : controller[v.name]); | ||
}); | ||
} | ||
middlewares.push(function *fillRespondBody(next) { | ||
const body = yield controller[actionName]; | ||
if (typeof body !== 'undefined') { | ||
controller.body = body; | ||
} | ||
yield next; | ||
}); | ||
co.wrap(compose(middlewares)).call(controller).then(function () { | ||
controller.respond(); | ||
}).catch(function (err) { | ||
self.handleError(req, res, convertToError(err)); | ||
}); | ||
}; | ||
} | ||
handleError(req, res, err) { | ||
if (res.headerSent || !res.socket || !res.socket.writable) { | ||
err.headerSent = true; | ||
return; | ||
} | ||
// unset all headers | ||
res._headers = {}; | ||
// force text/plain | ||
res.setHeader('Content-Type', 'text/plain'); | ||
// ENOENT support | ||
if (err.code === 'ENOENT') { | ||
err.status = 404; | ||
} | ||
// default to 500 | ||
if (typeof err.status !== 'number' || !statuses[err.status]) { | ||
err.status = 500; | ||
} | ||
// respond | ||
const code = statuses[err.status]; | ||
const msg = err.expose ? err.message : code; | ||
res.statusCode = code; | ||
res.setHeader('Content-Length', Buffer.byteLength(msg)); | ||
res.end(msg); | ||
} | ||
} | ||
function convertToError(err) { | ||
return err instanceof Error ? err : new Error(`non-error thrown: ${err}`); | ||
} | ||
module.exports = BayApplication; | ||
exports.exceptions = exceptions; |
'use strict'; | ||
const delegate = require('delegates'); | ||
const requestLib = require('./lib/request'); | ||
const responseLib = require('./lib/response'); | ||
const Cookies = require('cookies'); | ||
const accepts = require('accepts'); | ||
const createError = require('http-errors'); | ||
const httpAssert = require('http-assert'); | ||
const Stream = require('stream'); | ||
const isJSON = require('koa-is-json'); | ||
const statuses = require('statuses'); | ||
class BayController { | ||
constructor(ctx) { | ||
this.ctx = ctx; | ||
function BayController(app, req, res) { | ||
const request = this.request = Object.create(requestLib); | ||
const response = this.response = Object.create(responseLib); | ||
delegate(this, 'ctx') | ||
.getter('request') | ||
.getter('response') | ||
.getter('params') | ||
.getter('route') | ||
.getter('cookies'); | ||
this.app = request.app = response.app = app; | ||
this.req = request.req = response.req = req; | ||
this.res = request.res = response.res = res; | ||
/** | ||
* Response delegation. | ||
*/ | ||
request.ctx = response.ctx = this; | ||
request.response = response; | ||
response.request = request; | ||
delegate(this, 'response') | ||
.method('attachment') | ||
.method('redirect') | ||
.method('remove') | ||
.method('vary') | ||
.method('set') | ||
.method('append') | ||
.access('status') | ||
.access('message') | ||
.access('length') | ||
.access('type') | ||
.access('lastModified') | ||
.access('etag') | ||
.access('body') | ||
.getter('headerSent') | ||
.getter('writable'); | ||
this.originalUrl = request.originalUrl = req.url; | ||
this.cookies = new Cookies(req, res, this.keys); | ||
this.accept = request.accept = accepts(req); | ||
this.state = {}; | ||
} | ||
/** | ||
* Request delegation. | ||
*/ | ||
/** | ||
* Return JSON representation. | ||
* | ||
* Here we explicitly invoke .toJSON() on each | ||
* object, as iteration will otherwise fail due | ||
* to the getters and cause utilities such as | ||
* clone() to fail. | ||
* | ||
* @return {Object} | ||
* @api public | ||
*/ | ||
BayController.prototype.toJSON = function () { | ||
return { | ||
request: this.request.toJSON(), | ||
response: this.response.toJSON(), | ||
app: this.app.toJSON(), | ||
originalUrl: this.originalUrl, | ||
req: '<original node req>', | ||
res: '<original node res>', | ||
socket: '<original node socket>' | ||
}; | ||
}; | ||
delegate(this, 'request') | ||
.method('acceptsLanguages') | ||
.method('acceptsEncodings') | ||
.method('acceptsCharsets') | ||
.method('accepts') | ||
.method('get') | ||
.method('is') | ||
.access('querystring') | ||
.access('idempotent') | ||
.access('socket') | ||
.access('search') | ||
.access('method') | ||
.access('query') | ||
.access('path') | ||
.access('url') | ||
.getter('origin') | ||
.getter('href') | ||
.getter('subdomains') | ||
.getter('protocol') | ||
.getter('host') | ||
.getter('hostname') | ||
.getter('header') | ||
.getter('headers') | ||
.getter('secure') | ||
.getter('stale') | ||
.getter('fresh') | ||
.getter('ips') | ||
.getter('ip'); | ||
/** | ||
* Similar to .throw(), adds assertion. | ||
* | ||
* this.assert(this.user, 401, 'Please login!'); | ||
* | ||
* See: https://github.com/jshttp/http-assert | ||
* | ||
* @param {Mixed} test | ||
* @param {Number} [status=400] | ||
* @param {String} message | ||
* @api public | ||
*/ | ||
BayController.prototype.assert = function (res, code, message) { | ||
if (arguments.length === 1 || (arguments.length === 2 && typeof code === 'string')) { | ||
return httpAssert(res, 400, code); | ||
} | ||
return httpAssert(res, code, message); | ||
}; | ||
aroundAction(name, options) { | ||
if (!options) { | ||
options = {}; | ||
} | ||
options.name = name; | ||
if (!this._middleware) { | ||
this._middleware = []; | ||
} | ||
this._middleware.push(options); | ||
/** | ||
* Throw an error with `msg` and optional `status` | ||
* defaulting to 500. Note that these are user-level | ||
* errors, and the message may be exposed to the client. | ||
* | ||
* this.throw(403) | ||
* this.throw('name required', 400) | ||
* this.throw(400, 'name required') | ||
* this.throw('something exploded') | ||
* this.throw(new Error('invalid'), 400); | ||
* this.throw(400, new Error('invalid')); | ||
* | ||
* See: https://github.com/jshttp/http-errors | ||
* | ||
* @param {String|Number|Error} err, msg or status | ||
* @param {String|Number|Error} [err, msg or status] | ||
* @param {Object} [props] | ||
* @api public | ||
*/ | ||
BayController.prototype.throw = function (code, message) { | ||
if (arguments.length === 1 && typeof code === 'string') { | ||
throw createError(400, code); | ||
} | ||
throw createError(code, message); | ||
}; | ||
/** | ||
* Node-style asserting with HTTP errors being thrown | ||
* | ||
* This patch defaults the http code to 400 | ||
* rather than 500 | ||
*/ | ||
assert(res, code, message) { | ||
if (arguments.length === 1 || (arguments.length === 2 && typeof code === 'string')) { | ||
return this.ctx.assert(res, 400, code); | ||
} | ||
return this.ctx.assert(res, code, message); | ||
/** | ||
* Response helper. | ||
*/ | ||
BayController.prototype.respond = function () { | ||
const res = this.res; | ||
if (res.headersSent || !this.writable) { | ||
return; | ||
} | ||
/** | ||
* Like #assert, but unconditionally throws | ||
*/ | ||
throw(code, message) { | ||
if (arguments.length === 1 && typeof code === 'string') { | ||
return this.ctx.throw(400, code); | ||
let body = this.body; | ||
const code = this.status; | ||
// ignore body | ||
if (statuses.empty[code]) { | ||
// strip headers | ||
this.body = null; | ||
return res.end(); | ||
} | ||
if (this.method === 'HEAD') { | ||
if (isJSON(body)) { | ||
this.length = Buffer.byteLength(JSON.stringify(body)); | ||
} | ||
return this.ctx.throw(code, message); | ||
return res.end(); | ||
} | ||
} | ||
// status body | ||
if (body === null || typeof body === 'undefined') { | ||
this.type = 'text'; | ||
body = this.message || String(code); | ||
this.length = Buffer.byteLength(body); | ||
return res.end(body); | ||
} | ||
// responses | ||
if (Buffer.isBuffer(body)) { | ||
return res.end(body); | ||
} | ||
if (typeof body === 'string') { | ||
return res.end(body); | ||
} | ||
if (body instanceof Stream) { | ||
return body.pipe(res); | ||
} | ||
// body: json | ||
body = JSON.stringify(body); | ||
this.length = Buffer.byteLength(body); | ||
res.end(body); | ||
}; | ||
BayController.prototype.aroundAction = function (name, options) { | ||
if (!options) { | ||
options = {}; | ||
} | ||
options.name = name; | ||
if (!this._middleware) { | ||
this._middleware = []; | ||
} | ||
this._middleware.push(options); | ||
}; | ||
/** | ||
* Response delegation. | ||
*/ | ||
delegate(BayController.prototype, 'response') | ||
.method('attachment') | ||
.method('redirect') | ||
.method('remove') | ||
.method('vary') | ||
.method('set') | ||
.method('append') | ||
.access('status') | ||
.access('message') | ||
.access('body') | ||
.access('length') | ||
.access('type') | ||
.access('lastModified') | ||
.access('etag') | ||
.getter('headerSent') | ||
.getter('writable'); | ||
/** | ||
* Request delegation. | ||
*/ | ||
delegate(BayController.prototype, 'request') | ||
.method('acceptsLanguages') | ||
.method('acceptsEncodings') | ||
.method('acceptsCharsets') | ||
.method('accepts') | ||
.method('get') | ||
.method('is') | ||
.access('querystring') | ||
.access('idempotent') | ||
.access('socket') | ||
.access('search') | ||
.access('method') | ||
.access('query') | ||
.access('path') | ||
.access('url') | ||
.getter('origin') | ||
.getter('href') | ||
.getter('subdomains') | ||
.getter('protocol') | ||
.getter('host') | ||
.getter('hostname') | ||
.getter('header') | ||
.getter('headers') | ||
.getter('secure') | ||
.getter('stale') | ||
.getter('fresh') | ||
.getter('ips') | ||
.getter('ip'); | ||
module.exports = BayController; |
@@ -7,3 +7,3 @@ 'use strict'; | ||
class Route { | ||
constructor (methods, path, handler, options) { | ||
constructor(methods, path, handler, options) { | ||
this.options = _.defaults(options ? _.clone(options) : {}, { | ||
@@ -18,3 +18,11 @@ middleware: [] | ||
this.regexp = pathToRegexp(path, this.keys = []); | ||
this.handler = handler; | ||
if (typeof handler === 'string') { | ||
const key = handler.split('#'); | ||
this.handler = { | ||
controller: key[0], | ||
action: key[1] | ||
}; | ||
} else { | ||
this.handler = handler; | ||
} | ||
} | ||
@@ -40,3 +48,3 @@ | ||
return { | ||
params: params, | ||
params, | ||
path: m[0], | ||
@@ -43,0 +51,0 @@ middlewares: this.options.middleware, |
{ | ||
"name": "bay", | ||
"version": "0.0.2", | ||
"version": "0.1.1", | ||
"description": "The framework", | ||
@@ -15,14 +15,30 @@ "main": "index.js", | ||
"dependencies": { | ||
"debug": "^2.2.0", | ||
"accepts": "^1.3.0", | ||
"co": "^4.6.0", | ||
"content-disposition": "^0.5.0", | ||
"content-type": "^1.0.1", | ||
"cookies": "^0.5.1", | ||
"delegates": "^1.0.0", | ||
"destroy": "^1.0.3", | ||
"error-inject": "^1.0.0", | ||
"escape-html": "^1.0.3", | ||
"filter-match": "0.0.3", | ||
"fresh": "^0.3.0", | ||
"http-assert": "^1.1.1", | ||
"http-errors": "^1.3.1", | ||
"i": "^0.3.3", | ||
"koa": "^1.1.2", | ||
"koa-compose": "^2.3.0", | ||
"koa-is-json": "^1.0.0", | ||
"lodash": "^3.10.1", | ||
"methods": "^1.1.1", | ||
"mime-types": "^2.1.8", | ||
"on-finished": "^2.3.0", | ||
"parseurl": "^1.3.0", | ||
"path-to-regexp": "^1.2.1", | ||
"require-dir": "^0.3.0", | ||
"resolve-keypath": "^1.1.0" | ||
"resolve-keypath": "^1.1.0", | ||
"statuses": "^1.2.1", | ||
"type-is": "^1.6.10", | ||
"vary": "^1.1.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
44954
1512
27
11
2
4
+ Addedaccepts@^1.3.0
+ Addedco@^4.6.0
+ Addedcontent-disposition@^0.5.0
+ Addedcontent-type@^1.0.1
+ Addedcookies@^0.5.1
+ Addeddestroy@^1.0.3
+ Addederror-inject@^1.0.0
+ Addedescape-html@^1.0.3
+ Addedfresh@^0.3.0
+ Addedhttp-assert@^1.1.1
+ Addedhttp-errors@^1.3.1
+ Addedkoa-is-json@^1.0.0
+ Addedmime-types@^2.1.8
+ Addedon-finished@^2.3.0
+ Addedparseurl@^1.3.0
+ Addedstatuses@^1.2.1
+ Addedtype-is@^1.6.10
+ Addedvary@^1.1.0
+ Addedcookies@0.5.1(transitive)
+ Addedfresh@0.3.0(transitive)
+ Addedkeygrip@1.0.3(transitive)
- Removeddebug@^2.2.0
- Removedkoa@^1.1.2
- Removedany-promise@1.3.0(transitive)
- Removedcomposition@2.3.0(transitive)
- Removedcookies@0.8.0(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddepd@2.0.0(transitive)
- Removedfresh@0.5.2(transitive)
- Removedkeygrip@1.1.0(transitive)
- Removedkoa@1.7.0(transitive)
- Removedms@2.0.0(transitive)
- Removedonly@0.0.2(transitive)
- Removedtsscmp@1.0.6(transitive)