diff --git a/.travis.yml b/.travis.yml index 6f69b86..b03ae6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: -- '4.3.2' - '6.10.0' +- '8.11.1' script: - npm run unit-tests diff --git a/README.md b/README.md index cf2a06a..1eced9c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # lambda-wrapper -[![Build Status](https://travis-ci.org/SC5/lambda-wrapper.svg?branch=master)](https://travis-ci.org/SC5/lambda-wrapper) +[![Build Status](https://travis-ci.org/nordcloud/lambda-wrapper.svg?branch=master)](https://travis-ci.org/nordcloud/lambda-wrapper) Wrapper for running lambda modules locally or from AWS during development @@ -34,6 +34,8 @@ If you want to pass a custom context to the Lambda module (only when running loc lambda.runHandler(event, customContext, callback) Documentation for valid propreties in the Lambda context object are documented here http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html + +Exceptions from within the module will be returned as errors via the callback / promise. ## Development @@ -46,6 +48,9 @@ to your AWS account as 'lambdaWrapper-test'. ## Release History +* 2018/05/07 - v0.3.0 - Support for async calls, improved input validation +* 2017/07/09 - v0.2.0 - Return exceptions as errors via callback +* 2017/06/16 - v0.1.3 - Allow context object in run * 2016/10/21 - v0.1.2 - Support for using promises * 2016/07/26 - v0.1.1 - Support for alternative handler. runHandler method for passing custom context. * 2016/04/26 - v0.1.0 - Support for running lambda functions also from AWS @@ -56,5 +61,5 @@ to your AWS account as 'lambdaWrapper-test'. ## License -Copyright (c) 2016 [SC5](http://sc5.io/), licensed for users and contributors under MIT license. -https://github.com/SC5/lambda-wrapper/blob/master/LICENSE +Copyright (c) 2016 [Nordcloud](https://nordcloud.com/), licensed for users and contributors under MIT license. +https://github.com/nordcloud/lambda-wrapper/blob/master/LICENSE diff --git a/index.js b/index.js index 7e5ecd3..8e5bfc3 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,11 @@ class Wrapped { if (mod[handler]) { this.handler = mod[handler]; + } else { + if (!this.lambdaModule.lambdaFunction) { + throw new Error("Invalid input. Expected either lambda module (with exposed handler) " + + "or AWS Lambda configuration (with provided 'lambdaFunction' name)"); + } } } @@ -36,8 +41,22 @@ class Wrapped { const lambdaContext = Object.assign({}, defaultContext, customContext); try { - if (this.handler) { - this.handler(event, lambdaContext, callback); + if (this.handler) { + const handlerResult = this.handler(event, lambdaContext, callback); + // Check if the result itself is a promise + if (handlerResult && handlerResult.then) { + handlerResult.then(function(data) { + // Avoid Maximum call stack size exceeded exceptions + return setImmediate(function() { + callback(null, data); + }); + }).catch(function(err) { + // Avoid Maximum call stack size exceeded exceptions + return setImmediate(function() { + callback(err); + }); + }); + } } else { if (this.lambdaModule.region) { AWS.config.update({ @@ -57,12 +76,14 @@ class Wrapped { if (err) { return callback(err); } - + if (data.FunctionError) { + return callback(Object.assign(new Error(JSON.parse(data.Payload).errorMessage), data)); + } return callback(null, JSON.parse(data.Payload)); }); } } catch (ex) { - throw (ex); + return callback(ex); } }); } diff --git a/package.json b/package.json index 702d2a5..588957a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lambda-wrapper", - "version": "0.1.3", + "version": "0.3.0", "description": "Wrapper for running Amazon Lambda modules locally", "main": "index.js", "scripts": { @@ -9,27 +9,28 @@ }, "repository": { "type": "git", - "url": "https://github.com/SC5/lambda-wrapper.git" + "url": "https://github.com/nordcloud/lambda-wrapper.git" }, "keywords": [ "AWS", "lambda", "local" ], - "author": "Mikael Puittinen (mikael.puittinen@sc5.io)", + "author": "Mikael Puittinen (https://nordcloud.com)", "contributors": [ - "Toni Ruottu (https://sc5.io)", - "Eetu Tuomala (https://sc5.io)" + "Toni Ruottu", + "Eetu Tuomala" ], "license": "MIT", "bugs": { - "url": "https://github.com/SC5/lambda-wrapper/issues" + "url": "https://github.com/nordcloud/lambda-wrapper/issues" }, - "homepage": "https://github.com/SC5/lambda-wrapper", + "homepage": "https://github.com/nordcloud/lambda-wrapper", "devDependencies": { - "aws-sdk": "^2.4.0", "chai": "3.5.0", "mocha": "2.4.5" }, - "dependencies": {} + "dependencies": { + "aws-sdk": "^2.4.0" + } } diff --git a/test/async/async-wrapper.js b/test/async/async-wrapper.js new file mode 100644 index 0000000..2907e3a --- /dev/null +++ b/test/async/async-wrapper.js @@ -0,0 +1,82 @@ +"use strict"; + +const wrapper = require("../../index.js"); +const expect = require("chai").expect; + +const testMod7 = { + handler: async (event, context) => { + if (event.test === "success") { + return "Success"; + } + if (event.test === "fail") { + throw new Error("Fail"); + } + } +}; + +const testMod8 = { + handler: async (event, context, callback) => { + if (event.test === "success") { + callback(null, "Success"); + } + if (event.test === "fail") { + callback(new Error("Fail")); + } + } +}; + +describe("lambda wrapper local async", () => { + it("wrap + run async module 7 - await", async () => { + const w = wrapper.wrap(testMod7); + + const response = await w.run({ test: "success" }); + expect(response).to.be.equal("Success"); + }); + + it("wrap + run async module 7 - promise", done => { + const w = wrapper.wrap(testMod7); + + w.run({ test: "success" }).then(response => { + expect(response).to.be.equal("Success"); + done(); + }).catch(done); + }); + + it("wrap + run async module 7 - exception", async () => { + const w = wrapper.wrap(testMod7); + + try { + const response = await w.run({ test: "fail" }); + expect(response).to.be.null; + } catch (err) { + expect(err).to.be.instanceof(Error); + } + }); + + it("wrap + run async module 8 (callback) - await", async () => { + const w = wrapper.wrap(testMod8); + + const response = await w.run({ test: "success" }); + expect(response).to.be.equal("Success"); + }); + + it("wrap + run async module 8 (callback) - promise", done => { + const w = wrapper.wrap(testMod8); + + w.run({ test: "success" }).then(response => { + expect(response).to.be.equal("Success"); + done(); + }).catch(done); + }); + + it("wrap + run async module 8 (callback) - exception", async () => { + const w = wrapper.wrap(testMod8); + + try { + const response = await w.run({ test: "fail" }); + expect(response).to.be.null; + } catch (err) { + expect(err).to.be.instanceof(Error); + } + }); +}); diff --git a/test/test-wrapper.js b/test/test-wrapper.js index e7fac08..b6afde7 100644 --- a/test/test-wrapper.js +++ b/test/test-wrapper.js @@ -3,6 +3,8 @@ const wrapper = require('../index.js'); const expect = require('chai').expect; +const SUPPORTS_ASYNC = Number(process.versions.node.split('.', 1)[0]) >= 8; + const testMod1 = { handler: (event, context) => { if (event.test === 'success') { @@ -40,6 +42,13 @@ const testMod5 = { } }; +// causes exception +const testMod6 = { + handler: (event, context, callback) => { + throw 'TestException'; + } +} + describe('lambda-wrapper local', () => { it('init + run with success - callback', (done) => { wrapper.init(testMod1); @@ -202,6 +211,24 @@ describe('lambda-wrapper local', () => { }) .catch(done); }); + + it('wrap + run module 6 - exception', (done) => { + const w = wrapper.wrap(testMod6); + + w.run({ test: 'cbsuccess' }, { functionName: 'testing' }) + .then((response) => { + done('Did not return error'); + }, (error) => { + expect(error).to.be.equal('TestException'); + done(); + console.log(error); + }) + .catch(done); + }); + + if (SUPPORTS_ASYNC) { + require('./async/async-wrapper'); + } }); if (process.env.RUN_LIVE) { @@ -221,12 +248,12 @@ if (process.env.RUN_LIVE) { expect(response.event.test).to.be.equal('livesuccess'); done(); }); - }); + }).timeout(3000); it('can call lambda functions deployed in AWS - promise', (done) => { const w = wrapper.wrap({ lambdaFunction: 'lambdaWrapper-test', - region: process.env.AWS_DEFAULT_REGION || 'eu-central-1' + region: process.env.AWS_DEFAULT_REGION || 'us-east-1' }); w.run({ test: 'livesuccess' }) @@ -235,6 +262,6 @@ if (process.env.RUN_LIVE) { expect(response.event.test).to.be.equal('livesuccess'); done(); }).catch(done); - }); + }).timeout(3000); }); }