1 | <p align="center">
|
2 | <img height="140" src="https://avatars0.githubusercontent.com/u/36457275?s=400&u=16d355f384ed7f8e0655b7ed1d70ff2e411690d8&v=4e">
|
3 | <img height="140" src="https://user-images.githubusercontent.com/2955468/44874383-0168f780-ac69-11e8-8e51-774678cbd966.png">
|
4 | </p>
|
5 |
|
6 | <p align="center">
|
7 | <a href="https://npmjs.com/package/@manwaring/lambda-wrapper">
|
8 | <img src="https://flat.badgen.net/npm/v/@manwaring/lambda-wrapper?icon=npm&label=npm@latest"></a>
|
9 | <a href="https://www.npmjs.com/package/@manwaring/lambda-wrapper">
|
10 | <img src="https://flat.badgen.net/npm/dt/@manwaring/lambda-wrapper?icon=npm"></a>
|
11 | <a href="https://codecov.io/gh/manwaring/lambda-wrapper">
|
12 | <img src="https://flat.badgen.net/codecov/c/github/manwaring/lambda-wrapper/?icon=codecov"></a>
|
13 | <a href="https://packagephobia.now.sh/result?p=@manwaring/lambda-wrapper">
|
14 | <img src="https://flat.badgen.net/packagephobia/install/@manwaring/lambda-wrapper"></a>
|
15 | <a href="https://www.npmjs.com/package/@manwaring/lambda-wrapper">
|
16 | <img src="https://flat.badgen.net/npm/license/@manwaring/lambda-wrapper"></a>
|
17 | </p>
|
18 |
|
19 | <p align="center">
|
20 | <a href="https://circleci.com/gh/manwaring/lambda-wrapper">
|
21 | <img src="https://flat.badgen.net/circleci/github/manwaring/lambda-wrapper/master?icon=circleci"></a>
|
22 | <a href="https://flat.badgen.net/dependabot/manwaring/lambda-wrapper">
|
23 | <img src="https://flat.badgen.net/dependabot/manwaring/lambda-wrapper/?icon=dependabot&label=dependabot"></a>
|
24 | <a href="https://david-dm.org/manwaring/lambda-wrapper">
|
25 | <img src="https://flat.badgen.net/david/dep/manwaring/lambda-wrapper"></a>
|
26 | <a href="https://david-dm.org/manwaring/lambda-wrapper?type=dev">
|
27 | <img src="https://flat.badgen.net/david/dev/manwaring/lambda-wrapper/?label=dev+dependencies"></a>
|
28 | </p>
|
29 |
|
30 | # AWS Lambda wrapper library
|
31 |
|
32 | ### This documentation is for v3 of the library - [go here for v1](old-docs/v1/README.md) and [here for v2](old-docs/v2/README.md)
|
33 |
|
34 | 1. [Overview](#overview)
|
35 | 1. [Installation and setup](#installation-and-setup)
|
36 | - [Optional configuration](#optional-configuration)
|
37 | 1. [Supported events](#supported-events)
|
38 | - [API Gateway](#api-gateway)
|
39 | - [CloudFormation Custom Resource](#cloudformation-custom-resource)
|
40 | - [DynamoDB Stream](#dynamodb-stream)
|
41 | - [Lambda Authorizer](#lambda-authorizer)
|
42 | - [SNS](#sns)
|
43 | - [Generic event](#generic-event)
|
44 | 1. [Example projects](#example-projects)
|
45 |
|
46 | _Feedback appreciated! If you have an idea for how this library can be improved [please open an issue](https://github.com/manwaring/lambda-wrapper/issues/new)._
|
47 |
|
48 | # Overview
|
49 |
|
50 | ### TL;DR
|
51 |
|
52 | This library provides custom Lambda function wrappers which expose standard, abstracted functionality so that developers can focus on writing business logic instead of parsing event payloads and crafting response objects.
|
53 |
|
54 | ### Rationale and motivation
|
55 |
|
56 | AWS Lambda supports a wide variety of event triggers, each with unique payloads and expected responses. The Lambda execution environment, however, only provides the raw events and has no included mechanisms for simplifying response object creation. For example, API Gateway events include only the raw request body, leaving it up to developers to implement parsing themselves. Similarly, the developer is responsible for creating a response object which includes the correct HTTP status code and headers. Given the standard nature of these kinds of concerns, this library exposes helpful abstractions like parsed HTTP bodies based on content-type headers, and success response functions which create response objects with the correct status codes and headers for returning.
|
57 |
|
58 | # Installation and setup
|
59 |
|
60 | Install and save the package to `package.json` as a dependency:
|
61 |
|
62 | `npm i --save @manwaring/lambda-wrapper`
|
63 |
|
64 | `yarn add @manwaring/lambda-wrapper`
|
65 |
|
66 | ## Optional configuration
|
67 |
|
68 | If you want the wrapper to log request and response messages (helpful for debugging set an environemnt variable for `LAMBDA_WRAPPER_LOG=true`.
|
69 |
|
70 | If you want each invocation to be tagged with the AWS region, environment/, and Git revision simply set environment variables for each: `REGION=us-east-1`, `STAGE=prod`, `REVISION=f4ba682` (see [git-rev-sync](https://www.npmjs.com/package/git-rev-sync) and [serverless-plugin-git-variables](https://www.npmjs.com/package/serverless-plugin-git-variables) for libraries that can help you set git revision)
|
71 |
|
72 | # Supported events
|
73 |
|
74 | All of the events bellow have a corresponding wrapper which provides a deconstructed method signature exposing parsed/unmarshalled request parameters and helper response methods.
|
75 |
|
76 | 1. [API Gateway](#api-gateway) with support for cors headers and 200, 302, 400, and 500 responses
|
77 | 1. [CloudFormation Custom Resource](#cloudformation-custom-resource) with support for CloudFormation successes and failures
|
78 | 1. [DynamoDB Stream](#dynamodb-stream) with support for success and failure responses
|
79 | 1. [Lambda Authorizer](#lambda-authorizer) with support for creating access policies for successfully authorized requests
|
80 | 1. [SNS](#sns) with support for success and failure responses
|
81 | 1. [Generic event](#generic-event) wrapper with support for success and failure responses
|
82 |
|
83 | ## API Gateway
|
84 |
|
85 | ### Sample implementation
|
86 |
|
87 | ```ts
|
88 | import { api } from '@manwaring/lambda-wrapper';
|
89 |
|
90 | export const handler = api(async ({ body, path, success, error }) => {
|
91 | try {
|
92 | const { pathParam1, pathParam2 } = path;
|
93 | const results = await doSomething(body, pathParam1, pathParam2);
|
94 | return success(results);
|
95 | } catch (err) {
|
96 | return error(err);
|
97 | }
|
98 | });
|
99 | ```
|
100 |
|
101 | ### Properties and methods available on wrapper signature
|
102 |
|
103 | ```ts
|
104 | export interface ApiSignature {
|
105 | event: APIGatewayEvent; // original event
|
106 | body: any; // JSON parsed body payload if exists (otherwise null)
|
107 | path: { [name: string]: string }; // path param payload as key-value pairs if exists (otherwise null)
|
108 | query: { [name: string]: string }; // query param payload as key-value pairs if exists (otherwise null)
|
109 | headers: { [name: string]: string }; // header payload as key-value pairs if exists (otherwise null)
|
110 | testRequest: boolean; // indicates if this is a test request - looks for a header matching process.env.TEST_REQUEST_HEADER (dynamic from application) or 'Test-Request' (default)
|
111 | auth: any; // auth context from custom authorizer if exists (otherwise null)
|
112 | success(payload?: any, replacer?: (this: any, key: string, value: any) => any): ApiResponse; // returns 200 status code with optional payload as body
|
113 | invalid(errors?: string[]): ApiResponse; // returns 400 status code with optional errors as body
|
114 | notFound(message?: string): ApiResponse; // returns 404 status code with optional message as body
|
115 | notAuthorized(message?: string): ApiResponse; // returns 403 status code with optional message as body
|
116 | redirect(url: string): ApiResponse; // returns 302 status code (redirect) with new url
|
117 | error(error?: any): ApiResponse; // returns 500 status code with optional error as body
|
118 | }
|
119 |
|
120 | interface ApiResponse {
|
121 | statusCode: number;
|
122 | headers: { [name: string]: string | boolean };
|
123 | body?: string;
|
124 | }
|
125 | ```
|
126 |
|
127 | \*Note that each callback helper functions (success, invalid, redirect, error) includes CORS-enabling header information
|
128 |
|
129 | ## CloudFormation Custom Resource
|
130 |
|
131 | ### Sample implementation
|
132 |
|
133 | ```ts
|
134 | import { cloudFormation } from '@manwaring/lambda-wrapper';
|
135 |
|
136 | export const handler = cloudFormation(({ event, success, failure }) => {
|
137 | try {
|
138 | const { BucketName } = event.ResourceProperties;
|
139 | return success();
|
140 | } catch (err) {
|
141 | return failure(err);
|
142 | }
|
143 | });
|
144 | ```
|
145 |
|
146 | \*Note that currently the method wrapped by cloudFormation cannot be async - for reasons that aren't entirely clear to me when the method is async the requests to update CloudFormation with the correct action status fail, leaving a stack in the 'pending' state
|
147 |
|
148 | ### Properties and methods available on wrapper signature
|
149 |
|
150 | ```ts
|
151 | interface CloudFormationSignature {
|
152 | event: CloudFormationCustomResourceEvent; // original event
|
153 | success(payload?: any): void; // sends CloudFormation success event
|
154 | failure(message?: any): void; // sends CloudFormation failure event
|
155 | }
|
156 | ```
|
157 |
|
158 | ## DynamoDB Stream
|
159 |
|
160 | ### Sample implementation
|
161 |
|
162 | ```ts
|
163 | import { dynamodbStream } from '@manwaring/lambda-wrapper';
|
164 |
|
165 | export const handler = dynamodbStream(async ({ newVersions, success, error }) => {
|
166 | try {
|
167 | newVersions.forEach(version => console.log(version));
|
168 | return success(newVersions);
|
169 | } catch (err) {
|
170 | return error(err);
|
171 | }
|
172 | });
|
173 | ```
|
174 |
|
175 | ### Properties and methods available on wrapper signature
|
176 |
|
177 | ```ts
|
178 | interface DynamoDBStreamSignature {
|
179 | event: DynamoDBStreamEvent; // original event
|
180 | newVersions: any[]; // array of all unmarshalled javascript objects of new images
|
181 | oldVersions: any[]; // array of all unmarshalled javascript objects of old images
|
182 | versions: Version[]; // array of full version object (new image, old image, etc - see Version interface)
|
183 | success(message?: any): any; // logs and returns the message
|
184 | error(error?: any): void; // logs the error and throws it
|
185 | }
|
186 |
|
187 | interface Version {
|
188 | newVersion: any; // unmarshalled javascript object of new image (if exists) or null
|
189 | oldVersion: any; // unmarshalled javascript object of old image (if exists) or null
|
190 | keys: any; // unmarshalled javascript object of keys (includes key values)
|
191 | tableName: string; // name of the table the object came from
|
192 | tableArn: string; // arn of the table the object came from
|
193 | eventName: string; // name of the event (INSERT || MODIFY || REMOVE)
|
194 | }
|
195 | ```
|
196 |
|
197 | ## Lambda Authorizer
|
198 |
|
199 | ### Sample implementation
|
200 |
|
201 | ```ts
|
202 | import { authorizer } from '@manwaring/lambda-wrapper';
|
203 | const verifier = new Verifier(); // setup and configure JWT validation library
|
204 |
|
205 | export const handler = authorizer(async ({ token, valid, invalid }) => {
|
206 | try {
|
207 | if (!token) {
|
208 | return invalid('Missing token');
|
209 | }
|
210 | const jwt = await verifier.verifyAccessToken(token);
|
211 | return valid(jwt);
|
212 | } catch (err) {
|
213 | return invalid(err);
|
214 | }
|
215 | });
|
216 | ```
|
217 |
|
218 | ### Properties and methods available on wrapper signature
|
219 |
|
220 | ```ts
|
221 | interface AuthorizerSignature {
|
222 | event: CustomAuthorizerEvent; // original event
|
223 | token: string; // authorizer token from original event
|
224 | valid(jwt: any): Policy; // returns AWS policy to authenticate request, and adds auth context if available
|
225 | invalid(message?: any): void; // records invalid information and throws 401 unauthorized
|
226 | error(error?: any): void; // records error information and throws 401 unauthorized
|
227 | }
|
228 |
|
229 | interface Policy {
|
230 | principalId: string;
|
231 | policyDocument: {
|
232 | Version: string;
|
233 | Statement: {
|
234 | Action: string;
|
235 | Effect: string;
|
236 | Resource: string;
|
237 | }[];
|
238 | };
|
239 | }
|
240 | ```
|
241 |
|
242 | ## SNS
|
243 |
|
244 | ### Sample implementation
|
245 |
|
246 | ```ts
|
247 | import { sns } from '@manwaring/lambda-wrapper';
|
248 |
|
249 | export const handler = sns(async ({ message, success, error }) => {
|
250 | try {
|
251 | console.log(message);
|
252 | return success();
|
253 | } catch (err) {
|
254 | return error(err);
|
255 | }
|
256 | });
|
257 | ```
|
258 |
|
259 | ### Properties and methods available on wrapper signature
|
260 |
|
261 | ```ts
|
262 | interface SnsSignature {
|
263 | event: SNSEvent; // original event
|
264 | message: any; // JSON-parsed message from event
|
265 | success(message?: any): any; // logs and returns the message
|
266 | error(error?: any): void; // logs the error and throws
|
267 | }
|
268 | ```
|
269 |
|
270 | ## Generic event
|
271 |
|
272 | ### Sample implementation
|
273 |
|
274 | ```ts
|
275 | import { wrapper } from '@manwaring/lambda-wrapper';
|
276 |
|
277 | export const handler = wrapper(async ({ event, success, error }) => {
|
278 | try {
|
279 | const { value1, value2 } = event;
|
280 | const results = await doSomething(value1, value2);
|
281 | return success(results);
|
282 | } catch (err) {
|
283 | return error(err);
|
284 | }
|
285 | });
|
286 | ```
|
287 |
|
288 | ### Properties and methods available on wrapper signature
|
289 |
|
290 | ```ts
|
291 | interface WrapperSignature {
|
292 | event: any; // original event
|
293 | success(message?: any): any; // logs and returns the message
|
294 | error(error?: any): void; // logs the error and throws
|
295 | }
|
296 | ```
|
297 |
|
298 | # Example projects
|
299 |
|
300 | There is one [working example](examples) of how this package can be used in a simple 'hello world' serverless application:
|
301 |
|
302 | 1. [Using the Serverless Framework and TypeScript](examples/ts)
|