UNPKG

7.05 kBMarkdownView Raw
1[ ![Codeship Status for smallwins/lambda](https://codeship.com/projects/2e4082e0-d808-0133-2035-1eae90b9310e/status?branch=master)](https://codeship.com/projects/143109)
2
3---
4
5## :raised_hands::seedling: @smallwins/lambda λ
6
7- Author your AWS Lambda functions as pure node style callbacks (aka errbacks)
8- Familiar middleware pattern for composition
9- Event sources like DynamoDB triggers and SNS topics too
10- Helpful npm scripts `lambda-create`, `lambda-list`, `lambda-deploy` and `lambda-invoke`
11
12#### :satellite::satellite::satellite: λ returning json results :mailbox:
13
14Here is a vanilla AWS Lambda example for performing a sum. Given `event.query.x = 1` it will return `{count:2}`.
15
16```javascript
17exports.handler = function sum(event, context) {
18 var errors = []
19 if (typeof event.query === 'undefined') {
20 errors.push(ReferenceError('missing event.query'))
21 }
22 if (event.query && typeof event.query != 'object') {
23 errors.push(TypeError('event.query not an object'))
24 }
25 if (typeof event.query.x === 'undefined') {
26 errors.push(ReferenceError('event.query not an object'))
27 }
28 if (event.query.x && typeof event.query.x != 'number') {
29 errors.push(TypeError('event.query not an object'))
30 }
31 if (errors.length) {
32 // otherwise Error would return [{}, {}, {}, {}]
33 var err = errors.map(function(e) {return e.message})
34 context.fail(err)
35 }
36 else {
37 context.succeed({count:event.query.x + 1})
38 }
39}
40```
41
42A 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.
43
44We can do better:
45
46```javascript
47var validate = require('@smallwins/validate')
48var lambda = require('@smallwins/lambda')
49
50function sum(event, callback) {
51 var schema = {
52 'query': {required:true, type:Object},
53 'query.x': {required:true, type:Number}
54 }
55 var errors = validate(event, schema)
56 if (errors) {
57 callback(errors)
58 }
59 else {
60 var result = {count:event.query.x + 1}
61 callback(null, result)
62 }
63}
64
65exports.handler = lambda(sum)
66```
67
68`@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.
69
70#### :loop::loop::loop: easily chain dependant actions ala middleware :loop::loop::loop:
71
72Building on this foundation we can compose multiple errbacks into a Lambda. Lets compose a Lambda that:
73
74- Validates parameters
75- Checks for an authorized account
76- And then returns data safely
77- Or if anything fails return JSON serialized `Error` array
78
79```javascript
80var validate = require('@smallwins/validate')
81var lambda = require('@smallwins/lambda')
82
83function valid(event, callback) {
84 var schema = {
85 'body': {required:true, type:Object},
86 'body.username': {required:true, type:String},
87 'body.password': {required:true, type:String}
88 }
89 validate(event, schema, callback)
90}
91
92function authorized(event, callback) {
93 var loggedIn = event.body.username === 'sutro' && event.body.password === 'cat'
94 if (!loggedIn) {
95 // err first
96 callback(Error('not found'))
97 }
98 else {
99 // successful login
100 event.account = {
101 loggedIn: loggedIn,
102 name: 'sutro furry pants'
103 }
104 callback(null, event)
105 }
106}
107
108function safe(event, callback) {
109 callback(null, {account:event.account})
110}
111
112exports.handler = lambda(valid, authorized, safe)
113```
114
115In the example above our functions are executed in series passing event through each invocation. `valid` will pass event to `authorized` which in turn passes it to `save`. Any `Error` returns immediately so if we make it the last function we just send back the resulting account data. Clean!
116
117#### :floppy_disk: save a record from a dynamodb trigger :boom::gun:
118AWS DynamoDB can invoke a Lambda function if anything happens to a table.
119
120```javascript
121var lambda = require('@smallwins/lambda')
122
123function save(record, callback) {
124 console.log('save a version ', record)
125 callback(null, record)
126}
127
128exports.handler = lambda.sources.dynamo.save(save)
129```
130
131#### :love_letter: api :thought_balloon: :sparkles:
132
133- `lambda(...fns)`
134- `lambda.sources.dynamo.all(...fns)`
135- `lambda.sources.dynamo.save(...fns)`
136- `lambda.sources.dynamo.insert(...fns)`
137- `lambda.sources.dynamo.modify(...fns)`
138- `lambda.sources.dynamo.remove(...fns)`
139
140A handler looks something like this
141
142```javascript
143function handler(event, callback) {
144 // process event, use to pass data
145 var result = {ok:true, event:event}
146 callback(null, result)
147}
148```
149
150#### :heavy_exclamation_mark: regarding errors :x::interrobang:
151
152Good error handling makes your programs far easier to maintain. (This is a good guide.)[https://www.joyent.com/developers/node/design/errors]. When using `@smallwins/lambda` always use `Error` type as the first parameter to callback:
153
154```javascript
155function fails(event, callback) {
156 callback(Error('something went wrong')
157}
158```
159
160Or an `Error` array:
161
162```javascript
163function fails(event, callback) {
164 callback([
165 Error('missing email'),
166 Error('missing password')
167 ])
168}
169```
170
171`@smallwins/lambda` serializes error into Slack RPC style JSON making them easy to work from API Gateway:
172
173```javascript
174{
175 ok: false,
176 errors: [
177 {name:'Error', message:'missing email', stack'...'},
178 {name:'Error', message:'missing password', stack'...'}
179 ]
180}
181```
182
183#### <kbd>#! scripting api</kbd> :memo:
184
185`@smallwins/lambda` includes some helpful automation code perfect for npm scripts. If you have a project that looks like this:
186
187```
188project-of-lambdas/
189 |-test/
190 |-src/
191 | '-lambdas/
192 | |-signup/
193 | | |-index.js
194 | | |-test.js
195 | | '-package.json
196 | |-login/
197 | '-logout/
198 '-package.json
199
200```
201
202And a `package.json` like this:
203
204```javascript
205{
206 "name":"project-of-lambdas",
207 "scripts": {
208 "create":"AWS_PROFILE=smallwins lambda-create",
209 "list":"AWS_PROFILE=smallwins lambda-list",
210 "deploy":"AWS_PROFILE=smallwins lambda-deploy",
211 "invoke":"AWS_PROFILE=smallwins lambda-invoke",
212 "deps":"AWS_PROFILE=smallwins lambda-deps"
213 }
214}
215```
216
217- :point_right: <kbd>npm run create src/lambdas/forgot</kbd> creates a new lambda
218- :point_right: <kbd>npm run list</kbd> lists all deployed lambdas
219- :point_right: <kbd>npm run deploy src/lambdas/signup brian</kbd> deploys the lambda with the alias `brian`
220- :point_right:<kbd>npm run invoke src/lambdas/login brian '{"email":"b@brian.io", "pswd":"..."}'</kbd> to invoke a lambda
221- :point_right:<kbd>npm run deps src/lambdas/*</kbd> for a report of all your lambda deps
222
223The `./scripts/invoke.js` is also a module and can be useful for testing.
224
225```javscript
226var invoke = require('@smallwins/lambda/scripts/invoke')
227
228invoke('path/to/lambda', alias, payload, (err, response)=> {
229 console.log(err, response)
230})
231```
232