Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
swhite24 committed Apr 3, 2017
0 parents commit 4caea5c
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["syntax-async-functions", "transform-async-to-generator"]
}
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
45 changes: 45 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"parser": "babel-eslint",
"rules": {
"block-spacing": [ 2 ],
"brace-style": [ 2, "1tbs", {
"allowSingleLine": true
}],
"comma-dangle": [ 2, "never" ],
"comma-spacing": [ 2 ],
"curly": [ 2, "multi-line" ],
"eqeqeq": [ 2 ],
"indent": [ 2, 2, {
"SwitchCase": 1
}],
"key-spacing": [ 2 ],
"keyword-spacing": [ 2 ],
"linebreak-style": [ 2, "unix" ],
"no-console": [ 0 ],
"no-empty": [ 2, {
"allowEmptyCatch": true
}],
"no-else-return": [ 2 ],
"no-eval": [ 2 ],
"no-mixed-spaces-and-tabs": [ 2 ],
"no-multi-spaces": [ 2 ],
"no-spaced-func": [ 2 ],
"no-trailing-spaces": [ 2 ],
"no-unused-vars": [ 2 ],
"no-whitespace-before-property": [ 2 ],
"one-var": [ 2, {
"initialized": "never",
"uninitialized": "always"
}],
"quotes": [ 2, "single" ],
"semi": [ 2, "always" ],
"space-before-blocks": [ 2 ],
"space-before-function-paren": [ 2, "never" ],
"space-in-parens": [ 2 ]
},
"env": {
"node": true,
"es6": true
},
"extends": "eslint:recommended"
}
32 changes: 32 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
npm-debug.log

# Users Environment Variables
.lock-wscript

.DS_Store
dist
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/
node_modules/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# lambda-router
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "lambda-router",
"version": "1.0.0",
"description": "Lambda router for serverless framework",
"main": "index.js",
"scripts": {
"build": "babel -d dist src",
"watch": "babel -w -d dist src",
"prepublish": "npm run build",
"lint": "eslint src/",
"test": "mocha test/**/*.spec.js",
"test:watch": "mocha -w test/**/*.spec.js"
},
"author": "Steven White <[email protected]>",
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.24.0",
"babel-core": "^6.24.0",
"babel-eslint": "^7.2.1",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-async-to-generator": "^6.22.0",
"babel-runtime": "^6.23.0",
"chai": "^3.5.0",
"eslint": "^3.19.0",
"mocha": "^3.2.0",
"sinon": "^2.1.0",
"sinon-chai": "^2.9.0"
},
"dependencies": {
"boom": "^4.3.1"
}
}
157 changes: 157 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Lambda Router
*/

const Boom = require('boom');

class LambdaRouter {

/**
* LambdaRouter constructor.
* @param {Object} options
* @param {Object} options.headers
* @param {Function} options.onInvoke
* @param {Function} options.onError
*/
constructor(options = {}) {
this.routes = {};
this.headers = options.headers || {};
this.onInvoke = options.onInvoke;
this.onError = options.onError;
}

/**
* Lambda handler function.
* Maps event details to previously registered route handlers.
* @returns {Function}
*/
handler() {
return async (event, context, cb) => {
// Prevent callback waiting.
// IMPORTANT, otherwise lambda may potentially timeout
// needlessly.
context.callbackWaitsForEmptyEventLoop = false;

// Setup state object to allow handlers to pass data along
context.state = {};

// Find appropriate handlers
const route = this._divineRoute(event);

try {
// Verify route was found
if (!route || !route.handlers) throw Boom.notFound('Resource not found');

// Notify onInvoke handler if provided
if (this.onInvoke) this.onInvoke(event);

// Invoke handlers for reply
let payload;
for (let handler of route.handlers) {
payload = await handler(event, context);
}

// Deliver response
return cb(null, {
statusCode: 200,
headers: this.headers,
body: JSON.stringify(Object.assign({ success: true }, payload))
});
} catch (err) {
// Capture error details from boom
const details = err.output.payload;

// Get error body from onError handler if provided
let body;
if (this.onError) {
body = this.onError(err, event);
} else {
body = {
success: false,
error: Object.assign(err.data || {}, {
statusCode: details.statusCode || 400,
message: details.message,
code: details.error
})
};
}

// Deliver response
return cb(null, {
statusCode: details.statusCode || 400,
headers: this.headers,
body: JSON.stringify(body)
});
}

};
}

/**
* Add handler for get route.
* @param {String} path
* @param {Function} hdlr - list of handlers to call in succession
*/
get(path, ...hdlr) {
this._wrap('GET', path, hdlr);
}

/**
* Add handler for post route.
* @param {String} path
* @param {Function} hdlr - list of handlers to call in succession
*/
post(path, ...hdlr) {
this._wrap('POST', path, hdlr);
}

/**
* Add handler for put route.
* @param {String} path
* @param {Function} hdlr - list of handlers to call in succession
*/
put(path, ...hdlr) {
this._wrap('PUT', path, hdlr);
}

/**
* Add handler for delete route.
* @param {String} path
* @param {Function} hdlr - list of handlers to call in succession
*/
del(path, ...hdlr) {
this._wrap('DELETE', path, hdlr);
}

/**
* Add handler for options route.
* @param {String} path
* @param {Function} hdlr - list of handlers to call in succession
*/
options(path, ...hdlr) {
this._wrap('OPTIONS', path, hdlr);
}

/**
* Store route handler reference in routes
* @param {String} method
* @param {String} path
* @param {Function} hdlr - list of handlers to call in succession
*/
_wrap(method, path, handlers) {
if (!this.routes[method]) this.routes[method] = [];
this.routes[method].push({ path, handlers });
}

/**
* Deliver handler that matches lambda event.
* @param {Object} event
* @returns {Object}
*/
_divineRoute({ httpMethod, resource }) {
if (!this.routes[httpMethod]) return;
return this.routes[httpMethod].find(r => r.path === resource);
}
}

module.exports = LambdaRouter;

0 comments on commit 4caea5c

Please sign in to comment.