@smallwins/lambda
Advanced tools
Comparing version 4.9.0 to 4.11.0
{ | ||
"name": "@smallwins/lambda", | ||
"version": "4.9.0", | ||
"version": "4.11.0", | ||
"description": "Author your AWS Lambda functions as node style errbacks.", | ||
@@ -5,0 +5,0 @@ "main": "index", |
@@ -42,4 +42,6 @@ [ ![Codeship Status for smallwins/lambda](https://codeship.com/projects/2e4082e0-d808-0133-2035-1eae90b9310e/status?branch=master)](https://codeship.com/projects/143109) | ||
A huge amount of this code is working around quirky parameter validation. Builtin `Error` needs manual serialization (and you still lose the stack trace). The latter part of the code uses the funky AWS `context` object. | ||
A huge amount of vanilla Lambda code is working around quirky parameter validation. API Gateway gives you control over the parameters you can expect but this still means one or more of: headers, querystring paramters, form body, or url parameters. Event source style Lambdasare not much better because you get different payloads depending on the source. In the example above we are validating one query string parameter `x`. Imagine a big payload! 😮 | ||
Builtin `Error` needs manual serialization (and you still lose the stack trace). The latter part of the code uses the funky AWS `context` object. | ||
We can do better: | ||
@@ -69,3 +71,3 @@ | ||
`@smallwins/validate` takes care of parameter validation. The callback style above enjoys symmetry with the rest of Node and will automatically serialize `Error`s into JSON friendly objects including any stack trace. All you need to do is wrap a vanilla Node errback function in `lambda` which returns your function with an AWS Lambda friendly signature. | ||
`@smallwins/validate` cleans up parameter validation. The callback style above enjoys symmetry with the rest of Node and will automatically serialize `Error`s into JSON friendly objects including any stack trace. All you need to do is wrap a vanilla Node errback function in `lambda` which returns your function with an AWS Lambda friendly signature. | ||
@@ -120,4 +122,5 @@ #### :loop::loop::loop: easily chain dependant actions ala middleware :loop::loop::loop: | ||
#### :floppy_disk: save a record from a dynamodb trigger :boom::gun: | ||
AWS DynamoDB can invoke a Lambda function if anything happens to a table. | ||
AWS DynamoDB triggers invoke a Lambda function if anything happens to a table. The payload is usually a big array of records. `@smallwins/lambda` allows you to focus on processing a single record but executes the function in parallel on all the results in the Dynamo invocation. For convenience the same middleware chaining is supported. | ||
```javascript | ||
@@ -134,6 +137,8 @@ var lambda = require('@smallwins/lambda') | ||
#### :love_letter: api :thought_balloon: :sparkles: | ||
## :love_letter: api :thought_balloon::sparkles: | ||
- `lambda(...fns)` | ||
- `lambda.sources.dynamo.all(...fns)` | ||
- `lambda(...fns)` create a lambda that returns a serialized json result `{ok:true|false}` | ||
- `lambda([fns], callback)` create a lambda and handle result with your own errback formatter | ||
- `lambda.local(fn, fakeEvent, (err, result)=>)` run a lamda locally offline by faking the event obj | ||
- `lambda.sources.dynamo.all(...fns)` | ||
- `lambda.sources.dynamo.save(...fns)` | ||
@@ -140,0 +145,0 @@ - `lambda.sources.dynamo.insert(...fns)` |
#!/usr/bin/env node | ||
var aws = require('aws-sdk') | ||
var region = process.argv[2] || 'us-east-1' | ||
var region = process.env.AWS_REGION || 'us-east-1' | ||
var lambda = new aws.Lambda({region:region}) | ||
var chalk = require('chalk') | ||
var startsWith = require('lodash').startsWith | ||
var filtering = process.argv[2] || '' | ||
function log(txt) { | ||
console.log(chalk.green(' λ ') + chalk.cyan.underline.dim(txt)) | ||
} | ||
lambda.listFunctions({}, (err, fns)=> { | ||
var names = fns.Functions.map(f=> f.FunctionName).sort() | ||
names.forEach(name=> { | ||
console.log(chalk.green(' λ ') + chalk.yellow(name)) | ||
}) | ||
console.log(chalk.green(' λ ') + chalk.dim.grey('listing deployed lambdas' )) | ||
var names = fns.Functions.map(f=> f.FunctionName).sort().filter(name=> startsWith(name, filtering)) | ||
names.forEach(log) | ||
console.log(chalk.green(' λ ')) | ||
}) |
var async = require('async') | ||
var _ = require('lodash') | ||
var lodash = require('lodash') | ||
var errback = require('serialize-error') | ||
var isArray = lodash.isArray | ||
var isFunction = lodash.isFunction | ||
var reject = lodash.reject | ||
function lambda() { | ||
var firstRun = true // important to keep this here in this closure | ||
var fns = [].slice.call(arguments, 0) // grab all the functions | ||
var firstRun = true // important to keep this here in this closure | ||
var args = [].slice.call(arguments, 0) // grab the args | ||
// fail loudly for programmer not passing anything | ||
if (fns.length === 0) { | ||
if (args.length === 0) { | ||
throw Error('lambda requires at least one callback function') | ||
} | ||
// fail loud if the programmer passes something other than a fn | ||
var notOnlyFns = _.reject(fns, _.isFunction) | ||
if (notOnlyFns.length) { | ||
throw Error('lambda only accepts callback functions as arguments') | ||
// check for lambda([], (err, result)=>) sig | ||
var customFormatter = isArray(args[0]) && isFunction(args[1]) | ||
var fmt = customFormatter? args[1] : false | ||
var fns = fmt? args[0] : args | ||
// we only deal in function values around here | ||
var notOnlyFns = reject(fns, isFunction).length > 0 | ||
if (notOnlyFns) { | ||
throw Error('bad argument found: lambda(...fns) or lambda([...],(err, result)=>)') | ||
} | ||
@@ -40,17 +48,28 @@ | ||
// the real worker here | ||
async.waterfall(fns, function(err, result) { | ||
if (err) { | ||
// asummptions: | ||
// - err should be an array of Errors | ||
// - because lambda deals in json we need to serialize them | ||
var errors = (_.isArray(err)? err : [err]).map(errback) | ||
// deliberate use context.succeed; | ||
// there is no (good) use case for the (current) context.fail behavior (but happy to discuss in an issue)! | ||
context.succeed({ok:false, errors:errors}) | ||
// asummptions: | ||
// - err should be an array of Errors | ||
// - because lambda deals in json we need to serialize them | ||
function formatter(err, result) { | ||
if (fmt) { | ||
fmt(err, result, context) | ||
} | ||
else { | ||
if (err) { | ||
result = { | ||
ok: false, | ||
errors: (isArray(err)? err : [err]).map(errback) | ||
} | ||
} | ||
else { | ||
result.ok = true | ||
} | ||
// deliberate use context.succeed; | ||
// there is no (good) use case for the (current) context.fail behavior | ||
// (but happy to discuss in an issue)! | ||
context.succeed(result) | ||
} | ||
}) | ||
} | ||
// the real worker here | ||
async.waterfall(fns, formatter) | ||
} | ||
@@ -62,3 +81,5 @@ } | ||
* | ||
* var fn = lambda() | ||
* var fn = lambda(function (event, data) { | ||
* callback(null, {hello:'world'}) | ||
* }) | ||
* | ||
@@ -71,3 +92,3 @@ * // fake run locally | ||
* else { | ||
* console.log(result) | ||
* console.log(result) // logs: {ok:true, hello:'world'} | ||
* } | ||
@@ -74,0 +95,0 @@ * }) |
30594
18
797
238
8