UNPKG

31.2 kBMarkdownView Raw
1<p align="center">
2 <img height="150" src="https://d1wzvcwrgjaybe.cloudfront.net/repos/manwaring/lambda-wrapper/readme-category-icon.png">
3 <img height="150" src="https://d1wzvcwrgjaybe.cloudfront.net/repos/manwaring/lambda-wrapper/readme-repo-icon.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=@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?icon=packagephobia"></a>
15 <a href="https://www.npmjs.com/package/@manwaring/lambda-wrapper">
16 <img src="https://flat.badgen.net/github/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 <img height="0" width="0" src="https://b7z7o7y5fi.execute-api.us-east-1.amazonaws.com/v1/readme/visits/github/manwaring/lambda-wrapper?style=flat-square">
29</p>
30
31# AWS Lambda wrapper library
32
33### This documentation is for v4 of the library - [go here](old-docs) for [v1](old-docs/v1/README.md), [v2](old-docs/v2/README.md), and [v3](old-docs/v3/README.md) documentation.
34
351. [Overview](#overview)
361. [Installation and setup](#installation-and-setup)
371. [Supported events](#supported-events)
38 - [API Gateway](#api-gateway)
39 - [API Gateway HTTP API](#api-gateway-http-api)
40 - [CloudFormation Custom Resource](#cloudformation-custom-resource)
41 - [DynamoDB Stream](#dynamodb-stream)
42 - [Lambda Authorizer](#lambda-authorizer)
43 - [SNS](#sns)
44 - [Generic event](#generic-event)
451. [Example projects](#example-projects)
46
47# Overview
48
49### TL;DR
50
51This 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.
52
53### Rationale and motivation
54
55AWS Lambda supports a wide variety of event triggers, each with unique payloads and expected response objects. The Lambda method signature, however, only provides a raw event object and has no included mechanisms for simplifying payload parsing or 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. This library exposes helpful abstractions like parsed HTTP bodies based on content-type headers, and success functions which create response objects with the correct status codes and headers for returning to API Gateway.
56
57_Feedback is appreciated! If you have an idea for how this library can be improved (or just a complaint/criticism) then [please open an issue](https://github.com/manwaring/lambda-wrapper/issues/new)._
58
59# Installation and setup
60
61Install and save the package:
62
63`npm i -S @manwaring/lambda-wrapper`
64
65`yarn add @manwaring/lambda-wrapper`
66
67## Optional configuration
68
69If you want the wrapper to log request and response messages (helpful for debugging) set an environemnt variable for `LAMBDA_WRAPPER_LOG=true`.
70
71If you want each invocation to be tagged with the AWS region, stage/environment, and Git revision simply set environment variables for each and the library will pick them up, for example `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 automatically.
72
73# Supported events
74
75Each event listed here has a wrapper which provides a deconstructable method signature exposing parsed/unmarshalled request parameters and helper response methods.
76
771. [API Gateway](#api-gateway)
781. [API Gateway HTTP API](#api-gateway-http-api)
791. [CloudFormation Custom Resource](#cloudformation-custom-resource)
801. [DynamoDB Stream](#dynamodb-stream)
811. [Lambda Authorizer](#lambda-authorizer)
821. [SNS](#sns)
831. [Generic event](#generic-event) (a basic wrapper with support for success and failure responses)
84
85# API Gateway
86
87## Sample TypeScript implementation
88
89```ts
90import { api } from '@manwaring/lambda-wrapper';
91import { CustomInterface } from './custom-interface';
92import { doSomething } from './you-code';
93
94export const handler = api<CustomInterface>(async ({ body, path, success, invalid, error }) => {
95 try {
96 const { pathParam1, pathParam2 } = path;
97 if (!pathParam1) {
98 return invalid();
99 }
100 const results = await doSomething(body, pathParam1, pathParam2);
101 return success({ body: results });
102 } catch (err) {
103 return error({ err });
104 }
105});
106```
107
108By passing in CustomInterface as a generic type the method signature will cast the `body` object as an instance of CustomInterface, making TypeScript development easier. Note that the type is not required and the body property defaults to type `any`.
109
110<details>
111<summary>Sample implementation without generic</summary>
112
113```ts
114import { api } from '@manwaring/lambda-wrapper';
115import { doSomething } from './you-code';
116
117export const handler = api(async ({ body, path, success, invalid, error }) => {
118 try {
119 const { pathParam1, pathParam2 } = path;
120 if (!pathParam1) {
121 return invalid();
122 }
123 const results = await doSomething(body, pathParam1, pathParam2);
124 return success({ body: results });
125 } catch (err) {
126 return error({ err });
127 }
128});
129```
130</details>
131
132## Properties and methods available on wrapper signature
133
134<details open>
135<summary>Deconstructable wrapper signature</summary>
136
137Note that all properties are undefined if not present on the original request.
138
139```ts
140export interface ApiSignature<T = any> {
141 event: APIGatewayEvent; // original event provided by AWS
142 body: T; // body payload parsed according to content-type headers (or raw if no content-type headers found) and cast as T if provided (defaults to `any`)
143 websocket: WebsocketRequest; // websocket connection payload
144 path: { [name: string]: string }; // path params as key-value pairs
145 query: { [name: string]: string }; // query params as key-value pairs
146 headers: { [name: string]: string }; // headers as key-value pairs
147 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)
148 auth: any; // auth context from custom authorizer
149 success(params?: ResponseParameters): ApiResponse;
150 invalid(params?: ResponseParameters): ApiResponse;
151 notFound(params?: ResponseParameters): ApiResponse;
152 notAuthorized(params?: ResponseParameters): ApiResponse;
153 redirect(params: RedirectParameters): ApiResponse;
154 error(params?: ErrorParameters): ApiResponse;
155}
156```
157
158</details>
159
160<details>
161<summary>ApiResponse</summary>
162
163```ts
164interface ApiResponse {
165 statusCode: number;
166 headers: { [name: string]: any };
167 body?: string;
168}
169```
170
171</details>
172
173<details>
174<summary>ResponseParameters</summary>
175
176```ts
177interface ResponseParameters {
178 body?: any; // response body
179 json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
180 cors?: boolean; // indicates if CORS headers should be added, defaults to true
181 statusCode?: number; // status code to return, defaults by callback (success: 200, invalid: 400, notFound: 404, notAuthorized: 401, redirect: 302, error: 500)
182 headers?: { [key: string]: any }; // custom headers to include
183}
184```
185
186</details>
187
188<details>
189<summary>RedirectParameters</summary>
190
191```ts
192interface RedirectParameters {
193 url: string; // url to redirect to
194 cors?: boolean; // indicates if CORS headers should be added, defaults to true
195 statusCode?: number; // status code to return, defaults to 302
196 headers?: { [key: string]: any }; // custom headers to include
197}
198```
199
200</details>
201
202<details>
203<summary>ErrorParameters</summary>
204
205```ts
206interface ErrorParameters {
207 body?: any; // response body
208 json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
209 cors?: boolean; // indicates if CORS headers should be added, defaults to true
210 statusCode?: number; // status code to return, defaults to 500
211 headers?: { [key: string]: any }; // custom headers to include
212 err?: Error; // optional Error object for automatic logging
213}
214```
215
216</details>
217
218<details>
219<summary>WebsocketRequest</summary>
220
221```ts
222export interface WebsocketRequest {
223 accountId: string;
224 apiId: string;
225 connectedAt?: number;
226 connectionId?: string;
227 domainName?: string;
228 domainPrefix?: string;
229 eventType?: string;
230 extendedRequestId?: string;
231 protocol: string;
232 httpMethod: string;
233 identity: APIGatewayEventIdentity;
234 messageDirection?: string;
235 messageId?: string | null;
236 path: string;
237 stage: string;
238 requestId: string;
239 requestTime?: string;
240 requestTimeEpoch: number;
241 resourceId: string;
242 resourcePath: string;
243 routeKey?: string;
244}
245```
246
247</details>
248
249## Response functions
250
251<details open>
252<summary>Success</summary>
253
254### Available parameters
255
256```ts
257{
258 body?: any,
259 json?: boolean,
260 cors?: boolean,
261 statusCode?: number,
262 headers?: { [key: string]: any}
263}
264```
265
266### Default parameters
267
268```ts
269{
270 json: true,
271 cors: true,
272 statusCode: 200
273}
274```
275
276### Invocation with defaults
277
278```ts
279const response = { hello: 'world' };
280return success({ body: response });
281
282// returns
283{
284 body: "{\"hello\":\"world\"}",
285 statusCode: 200,
286 headers: {
287 'Access-Control-Allow-Origin': '*',
288 'Access-Control-Allow-Credentials': true,
289 'Content-Type': 'application/json'
290 }
291}
292```
293
294### Invocation overriding defaults
295
296```ts
297const response = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
298const headers = { 'Content-Type': 'image/svg+xml' };
299return success({ body: response, json: false, cors: false, headers });
300
301// returns
302{
303 body: "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>",
304 statusCode: 200,
305 headers: { 'Content-Type': 'image/svg+xml' }
306}
307```
308
309</details>
310
311<details>
312<summary>Invalid</summary>
313
314### Available parameters
315
316```ts
317{
318 body?: any,
319 json?: boolean,
320 cors?: boolean,
321 statusCode?: number,
322 headers?: { [key: string]: any}
323}
324```
325
326### Default parameters
327
328```ts
329{
330 json: true,
331 cors: true,
332 statusCode: 400
333}
334```
335
336### Invocation with defaults
337
338```ts
339return invalid();
340
341// returns
342{
343 statusCode: 400,
344 headers: {
345 'Access-Control-Allow-Origin': '*',
346 'Access-Control-Allow-Credentials': true,
347 }
348}
349```
350
351### Invocation overriding defaults
352
353```ts
354const response = { invalid: 'properties' };
355return invalid({ body: response, cors: false });
356
357// returns
358{
359 body: "{\"invalid\":\"properties\"}",
360 statusCode: 400,
361 headers: { 'Content-Type': 'application/json' }
362}
363```
364
365</details>
366
367<details>
368<summary>Not found</summary>
369
370### Available parameters
371
372```ts
373{
374 body?: any,
375 json?: boolean,
376 cors?: boolean,
377 statusCode?: number,
378 headers?: { [key: string]: any}
379}
380```
381
382### Default parameters
383
384```ts
385{
386 json: true,
387 cors: true,
388 statusCode: 404
389}
390```
391
392### Invocation with defaults
393
394```ts
395return notFound();
396
397// returns
398{
399 statusCode: 404,
400 headers: {
401 'Access-Control-Allow-Origin': '*',
402 'Access-Control-Allow-Credentials': true,
403 }
404}
405```
406
407### Invocation overriding defaults
408
409```ts
410const response = 'Not found';
411return notFound({ body: response, cors: false });
412
413// returns
414{
415 body: "Not found",
416 statusCode: 404,
417}
418```
419
420</details>
421
422<details>
423<summary>Not authorized</summary>
424
425### Available parameters
426
427```ts
428{
429 body?: any,
430 json?: boolean,
431 cors?: boolean,
432 statusCode?: number,
433 headers?: { [key: string]: any}
434}
435```
436
437### Default parameters
438
439```ts
440{
441 json: true,
442 cors: true,
443 statusCode: 401
444}
445```
446
447### Invocation with defaults
448
449```ts
450return notAuthorized();
451
452// returns
453{
454 statusCode: 401,
455 headers: {
456 'Access-Control-Allow-Origin': '*',
457 'Access-Control-Allow-Credentials': true,
458 }
459}
460```
461
462### Invocation overriding defaults
463
464```ts
465const response = 'Not authorized';
466return notAuthorized({ body: response, cors: false });
467
468// returns
469{
470 body: "Not Authorized",
471 statusCode: 401,
472}
473```
474
475</details>
476
477<details>
478<summary>Redirect</summary>
479
480### Available parameters
481
482```ts
483{
484 url: string,
485 cors?: boolean,
486 statusCode?: number,
487 headers?: { [key: string]: any}
488}
489```
490
491### Default parameters
492
493```ts
494{
495 cors: true,
496 statusCode: 302
497}
498```
499
500### Invocation with defaults
501
502```ts
503const url = 'https://github.com/manwaring/lambda-wrapper';
504return redirect({ url });
505
506// returns
507{
508 statusCode: 302,
509 headers: {
510 'Access-Control-Allow-Origin': '*',
511 'Access-Control-Allow-Credentials': true,
512 'Location': 'https://github.com/manwaring/lambda-wrapper'
513 }
514}
515```
516
517### Invocation overriding defaults
518
519```ts
520const url = 'https://github.com/manwaring/lambda-wrapper';
521return redirect({ url, statusCode: 308, cors: false });
522
523// returns
524{
525 statusCode: 308,
526 headers: {
527 'Location': 'https://github.com/manwaring/lambda-wrapper'
528 }
529}
530```
531
532</details>
533
534<details>
535<summary>Error</summary>
536
537### Available parameters
538
539```ts
540{
541 body?: any,
542 json?: boolean,
543 cors?: boolean,
544 statusCode?: number,
545 headers?: { [key: string]: any},
546 err?: Error;
547}
548```
549
550### Default parameters
551
552```ts
553{
554 json: true,
555 cors: true,
556 statusCode: 500
557}
558```
559
560### Invocation with defaults
561
562```ts
563return error();
564
565// returns
566{
567 statusCode: 500,
568 headers: {
569 'Access-Control-Allow-Origin': '*',
570 'Access-Control-Allow-Credentials': true,
571 }
572}
573```
574
575### Invocation overriding defaults
576
577```ts
578catch (err) {
579 const body = { error: 'Unexpected error' };
580 return error({ body, err });
581}
582
583// logs
584console.debug(err);
585
586// returns
587{
588 body: "{\"error\": \"Unexpected error\"}",
589 statusCode: 500,
590 headers: {
591 'Access-Control-Allow-Origin': '*',
592 'Access-Control-Allow-Credentials': true,
593 }
594}
595```
596
597</details>
598
599# API Gateway HTTP API
600
601Other than the raw payload from AWS the HTTP API method signature and response functions match the API Gateway signature and functions. Hooray for wrappers! Note that you still need to provide the correct wrapper function so that the library can parse the AWS event correctly.
602
603## Sample TypeScript implementation
604
605```ts
606import { httpApi } from '@manwaring/lambda-wrapper';
607import { CustomInterface } from './custom-interface';
608import { doSomething } from './you-code';
609
610export const handler = httpApi<CustomInterface>(async ({ body, path, success, invalid, error }) => {
611 try {
612 const { pathParam1, pathParam2 } = path;
613 if (!pathParam1) {
614 return invalid();
615 }
616 const results = await doSomething(body, pathParam1, pathParam2);
617 return success({ body: results });
618 } catch (err) {
619 return error({ err });
620 }
621});
622```
623
624By passing in CustomInterface as a generic type the method signature will cast the `body` object as an instance of CustomInterface, making TypeScript development easier. Note that the type is not required and the body property defaults to type `any`.
625
626<details>
627<summary>Sample implementation without generic</summary>
628
629```ts
630import { httpApi } from '@manwaring/lambda-wrapper';
631import { doSomething } from './you-code';
632
633export const handler = httpApi(async ({ body, path, success, invalid, error }) => {
634 try {
635 const { pathParam1, pathParam2 } = path;
636 if (!pathParam1) {
637 return invalid();
638 }
639 const results = await doSomething(body, pathParam1, pathParam2);
640 return success({ body: results });
641 } catch (err) {
642 return error({ err });
643 }
644});
645```
646</details>
647
648## Properties and methods available on wrapper signature
649
650<details open>
651<summary>Deconstructable wrapper signature</summary>
652
653Note that all properties are undefined if not present on the original request.
654
655```ts
656export interface HttpApiSignature<T = any> {
657 event: HttpApiEvent; // original event provided by AWS
658 body: T; // body payload parsed according to content-type headers (or raw if no content-type headers found) and cast as T if provided (defaults to `any`)
659 path: { [name: string]: string }; // path params as key-value pairs
660 query: { [name: string]: string }; // query params as key-value pairs
661 headers: { [name: string]: string }; // headers as key-value pairs
662 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)
663 auth: any; // auth context from JWT authorizer
664 success(params?: ResponseParameters): ApiResponse;
665 invalid(params?: ResponseParameters): ApiResponse;
666 notFound(params?: ResponseParameters): ApiResponse;
667 notAuthorized(params?: ResponseParameters): ApiResponse;
668 redirect(params: RedirectParameters): ApiResponse;
669 error(params?: ErrorParameters): ApiResponse;
670}
671```
672
673</details>
674
675<details>
676<summary>ApiResponse</summary>
677
678```ts
679interface ApiResponse {
680 statusCode: number;
681 headers: { [name: string]: any };
682 body?: string;
683}
684```
685
686</details>
687
688<details>
689<summary>ResponseParameters</summary>
690
691```ts
692interface ResponseParameters {
693 body?: any; // response body
694 json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
695 cors?: boolean; // indicates if CORS headers should be added, defaults to true
696 statusCode?: number; // status code to return, defaults by callback (success: 200, invalid: 400, notFound: 404, notAuthorized: 401, redirect: 302, error: 500)
697 headers?: { [key: string]: any }; // custom headers to include
698}
699```
700
701</details>
702
703<details>
704<summary>RedirectParameters</summary>
705
706```ts
707interface RedirectParameters {
708 url: string; // url to redirect to
709 cors?: boolean; // indicates if CORS headers should be added, defaults to true
710 statusCode?: number; // status code to return, defaults to 302
711 headers?: { [key: string]: any }; // custom headers to include
712}
713```
714
715</details>
716
717<details>
718<summary>ErrorParameters</summary>
719
720```ts
721interface ErrorParameters {
722 body?: any; // response body
723 json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
724 cors?: boolean; // indicates if CORS headers should be added, defaults to true
725 statusCode?: number; // status code to return, defaults to 500
726 headers?: { [key: string]: any }; // custom headers to include
727 err?: Error; // optional Error object for automatic logging
728}
729```
730
731</details>
732
733<details>
734<summary>HttpApiEvent</summary>
735
736_[AWS documentation of raw event](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format)_
737
738```ts
739export interface HttpApiEvent {
740 version: string;
741 routeKey: string;
742 rawPath: string;
743 rawQueryString: string;
744 cookies: string[];
745 headers: { [key: string]: string };
746 queryStringParameters: { [key: string]: string };
747 requestContext: {
748 accountId: string;
749 apiId: string;
750 authorizer: {
751 jwt: {
752 claims: { [key: string]: string };
753 scopes: string[];
754 };
755 };
756 domainName: string;
757 domainPrefix: string;
758 http: {
759 method: string;
760 path: string;
761 protocol: string;
762 sourceIp: string;
763 userAgent: string;
764 };
765 requestId: string;
766 routeKey: string;
767 stage: string;
768 time: string;
769 timeEpoch: number;
770 };
771 body: string;
772 pathParameters: { [key: string]: string };
773 isBase64Encoded: boolean;
774 stageVariables: { [key: string]: string };
775}
776```
777
778</details>
779
780## Response functions
781
782<details open>
783<summary>Success</summary>
784
785### Available parameters
786
787```ts
788{
789 body?: any,
790 json?: boolean,
791 cors?: boolean,
792 statusCode?: number,
793 headers?: { [key: string]: any}
794}
795```
796
797### Default parameters
798
799```ts
800{
801 json: true,
802 cors: true,
803 statusCode: 200
804}
805```
806
807### Invocation with defaults
808
809```ts
810const response = { hello: 'world' };
811return success({ body: response });
812
813// returns
814{
815 body: "{\"hello\":\"world\"}",
816 statusCode: 200,
817 headers: {
818 'Access-Control-Allow-Origin': '*',
819 'Access-Control-Allow-Credentials': true,
820 'Content-Type': 'application/json'
821 }
822}
823```
824
825### Invocation overriding defaults
826
827```ts
828const response = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
829const headers = { 'Content-Type': 'image/svg+xml' };
830return success({ body: response, json: false, cors: false, headers });
831
832// returns
833{
834 body: "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>",
835 statusCode: 200,
836 headers: { 'Content-Type': 'image/svg+xml' }
837}
838```
839
840</details>
841
842<details>
843<summary>Invalid</summary>
844
845### Available parameters
846
847```ts
848{
849 body?: any,
850 json?: boolean,
851 cors?: boolean,
852 statusCode?: number,
853 headers?: { [key: string]: any}
854}
855```
856
857### Default parameters
858
859```ts
860{
861 json: true,
862 cors: true,
863 statusCode: 400
864}
865```
866
867### Invocation with defaults
868
869```ts
870return invalid();
871
872// returns
873{
874 statusCode: 400,
875 headers: {
876 'Access-Control-Allow-Origin': '*',
877 'Access-Control-Allow-Credentials': true,
878 }
879}
880```
881
882### Invocation overriding defaults
883
884```ts
885const response = { invalid: 'properties' };
886return invalid({ body: response, cors: false });
887
888// returns
889{
890 body: "{\"invalid\":\"properties\"}",
891 statusCode: 400,
892 headers: { 'Content-Type': 'application/json' }
893}
894```
895
896</details>
897
898<details>
899<summary>Not found</summary>
900
901### Available parameters
902
903```ts
904{
905 body?: any,
906 json?: boolean,
907 cors?: boolean,
908 statusCode?: number,
909 headers?: { [key: string]: any}
910}
911```
912
913### Default parameters
914
915```ts
916{
917 json: true,
918 cors: true,
919 statusCode: 404
920}
921```
922
923### Invocation with defaults
924
925```ts
926return notFound();
927
928// returns
929{
930 statusCode: 404,
931 headers: {
932 'Access-Control-Allow-Origin': '*',
933 'Access-Control-Allow-Credentials': true,
934 }
935}
936```
937
938### Invocation overriding defaults
939
940```ts
941const response = 'Not found';
942return notFound({ body: response, cors: false });
943
944// returns
945{
946 body: "Not found",
947 statusCode: 404,
948}
949```
950
951</details>
952
953<details>
954<summary>Not authorized</summary>
955
956### Available parameters
957
958```ts
959{
960 body?: any,
961 json?: boolean,
962 cors?: boolean,
963 statusCode?: number,
964 headers?: { [key: string]: any}
965}
966```
967
968### Default parameters
969
970```ts
971{
972 json: true,
973 cors: true,
974 statusCode: 401
975}
976```
977
978### Invocation with defaults
979
980```ts
981return notAuthorized();
982
983// returns
984{
985 statusCode: 401,
986 headers: {
987 'Access-Control-Allow-Origin': '*',
988 'Access-Control-Allow-Credentials': true,
989 }
990}
991```
992
993### Invocation overriding defaults
994
995```ts
996const response = 'Not authorized';
997return notAuthorized({ body: response, cors: false });
998
999// returns
1000{
1001 body: "Not Authorized",
1002 statusCode: 401,
1003}
1004```
1005
1006</details>
1007
1008<details>
1009<summary>Redirect</summary>
1010
1011### Available parameters
1012
1013```ts
1014{
1015 url: string,
1016 cors?: boolean,
1017 statusCode?: number,
1018 headers?: { [key: string]: any}
1019}
1020```
1021
1022### Default parameters
1023
1024```ts
1025{
1026 cors: true,
1027 statusCode: 302
1028}
1029```
1030
1031### Invocation with defaults
1032
1033```ts
1034const url = 'https://github.com/manwaring/lambda-wrapper';
1035return redirect({ url });
1036
1037// returns
1038{
1039 statusCode: 302,
1040 headers: {
1041 'Access-Control-Allow-Origin': '*',
1042 'Access-Control-Allow-Credentials': true,
1043 'Location': 'https://github.com/manwaring/lambda-wrapper'
1044 }
1045}
1046```
1047
1048### Invocation overriding defaults
1049
1050```ts
1051const url = 'https://github.com/manwaring/lambda-wrapper';
1052return redirect({ url, statusCode: 308, cors: false });
1053
1054// returns
1055{
1056 statusCode: 308,
1057 headers: {
1058 'Location': 'https://github.com/manwaring/lambda-wrapper'
1059 }
1060}
1061```
1062
1063</details>
1064
1065<details>
1066<summary>Error</summary>
1067
1068### Available parameters
1069
1070```ts
1071{
1072 body?: any,
1073 json?: boolean,
1074 cors?: boolean,
1075 statusCode?: number,
1076 headers?: { [key: string]: any},
1077 err?: Error;
1078}
1079```
1080
1081### Default parameters
1082
1083```ts
1084{
1085 json: true,
1086 cors: true,
1087 statusCode: 500
1088}
1089```
1090
1091### Invocation with defaults
1092
1093```ts
1094return error();
1095
1096// returns
1097{
1098 statusCode: 500,
1099 headers: {
1100 'Access-Control-Allow-Origin': '*',
1101 'Access-Control-Allow-Credentials': true,
1102 }
1103}
1104```
1105
1106### Invocation overriding defaults
1107
1108```ts
1109catch (err) {
1110 const body = { error: 'Unexpected error' };
1111 return error({ body, err });
1112}
1113
1114// logs
1115console.debug(err);
1116
1117// returns
1118{
1119 body: "{\"error\": \"Unexpected error\"}",
1120 statusCode: 500,
1121 headers: {
1122 'Access-Control-Allow-Origin': '*',
1123 'Access-Control-Allow-Credentials': true,
1124 }
1125}
1126```
1127
1128</details>
1129
1130# CloudFormation Custom Resource
1131
1132## Sample TypeScript implementation
1133
1134```ts
1135import { cloudFormation } from '@manwaring/lambda-wrapper';
1136
1137export const handler = cloudFormation(({ event, success, failure }) => {
1138 try {
1139 const { BucketName } = event.ResourceProperties;
1140 return success();
1141 } catch (err) {
1142 return failure(err);
1143 }
1144});
1145```
1146
1147\*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
1148
1149## Properties and methods available on wrapper signature
1150
1151```ts
1152interface CloudFormationSignature {
1153 event: CloudFormationCustomResourceEvent; // original event
1154 success(payload?: any): void; // sends CloudFormation success event
1155 failure(message?: any): void; // sends CloudFormation failure event
1156}
1157```
1158
1159# DynamoDB Stream
1160
1161## Sample TypeScript implementation
1162
1163```ts
1164import { dynamodbStream } from '@manwaring/lambda-wrapper';
1165import { CustomInterface } from './custom-interface';
1166
1167// By passing in CustomInterface as a generic the async method signature will correctly identify newVersions as an array of CustomInterface, making TypeScript development easier (note that the generic is not required in JavaScript projects)
1168export const handler = dynamodbStream<CustomInterface>(async ({ newVersions, success, error }) => {
1169 try {
1170 newVersions.forEach((version) => console.log(version));
1171 return success(newVersions);
1172 } catch (err) {
1173 return error(err);
1174 }
1175});
1176
1177interface CustomInterface {
1178 id: number;
1179 value: string;
1180}
1181```
1182
1183## Properties and methods available on wrapper signature
1184
1185```ts
1186interface DynamoDBStreamSignature<T> {
1187 event: DynamoDBStreamEvent; // original event
1188 newVersions: T[]; // array of all unmarshalled javascript objects of new images
1189 oldVersions: T[]; // array of all unmarshalled javascript objects of old images
1190 versions: Version<T>[]; // array of full version object (new image, old image, etc - see Version interface)
1191 success(message?: any): any; // logs and returns the message
1192 error(error?: any): void; // logs the error and throws it
1193}
1194
1195interface Version<T> {
1196 newVersion: T; // unmarshalled javascript object of new image (if exists) or null
1197 oldVersion: T; // unmarshalled javascript object of old image (if exists) or null
1198 keys: any; // unmarshalled javascript object of keys (includes key values)
1199 tableName: string; // name of the table the object came from
1200 tableArn: string; // arn of the table the object came from
1201 eventName: 'INSERT' | 'MODIFY' | 'REMOVE'; // name of the event (INSERT || MODIFY || REMOVE)
1202}
1203```
1204
1205# Lambda Authorizer
1206
1207## Sample TypeScript implementation
1208
1209```ts
1210import { authorizer } from '@manwaring/lambda-wrapper';
1211const verifier = new Verifier(); // setup and configure JWT validation library
1212
1213export const handler = authorizer(async ({ token, valid, invalid }) => {
1214 try {
1215 if (!token) {
1216 return invalid('Missing token');
1217 }
1218 const jwt = await verifier.verifyAccessToken(token);
1219 return valid(jwt);
1220 } catch (err) {
1221 return invalid(err);
1222 }
1223});
1224```
1225
1226## Properties and methods available on wrapper signature
1227
1228```ts
1229interface AuthorizerSignature {
1230 event: CustomAuthorizerEvent; // original event
1231 token: string; // authorizer token from original event
1232 valid(jwt: any): Policy; // returns AWS policy to authenticate request, and adds auth context if available
1233 invalid(message?: any): void; // records invalid information and throws 401 unauthorized
1234 error(error?: any): void; // records error information and throws 401 unauthorized
1235}
1236
1237interface Policy {
1238 principalId: string;
1239 policyDocument: {
1240 Version: string;
1241 Statement: {
1242 Action: string;
1243 Effect: string;
1244 Resource: string;
1245 }[];
1246 };
1247}
1248```
1249
1250# SNS
1251
1252## Sample TypeScript implementation
1253
1254```ts
1255import { sns } from '@manwaring/lambda-wrapper';
1256import { CustomInterface } from './custom-interface';
1257
1258// By passing in CustomInterface as a generic the async method signature will correctly identify newVersions as an array of CustomInterface, making TypeScript development easier (note that the generic is not required in JavaScript projects)
1259export const handler = sns<CustomInterface>(async ({ message, success, error }) => {
1260 try {
1261 console.log(message);
1262 return success();
1263 } catch (err) {
1264 return error(err);
1265 }
1266});
1267```
1268
1269## Properties and methods available on wrapper signature
1270
1271```ts
1272interface SnsSignature {
1273 event: SNSEvent; // original event
1274 message: any; // JSON-parsed message from event
1275 success(message?: any): any; // logs and returns the message
1276 error(error?: any): void; // logs the error and throws
1277}
1278```
1279
1280# Generic event
1281
1282## Sample TypeScript implementation
1283
1284```ts
1285import { wrapper } from '@manwaring/lambda-wrapper';
1286import { CustomInterface } from './custom-interface';
1287
1288// By passing in CustomInterface as a generic the async method signature will correctly identify newVersions as an array of CustomInterface, making TypeScript development easier (note that the generic is not required in JavaScript projects)
1289export const handler = wrapper<CustomInterface>(async ({ event, success, error }) => {
1290 try {
1291 const { value1, value2 } = event;
1292 const results = await doSomething(value1, value2);
1293 return success(results);
1294 } catch (err) {
1295 return error(err);
1296 }
1297});
1298```
1299
1300## Properties and methods available on wrapper signature
1301
1302```ts
1303interface WrapperSignature<T> {
1304 event: T; // original event
1305 success(message?: any): any; // logs and returns the message
1306 error(error?: any): void; // logs the error and throws
1307}
1308```
1309
1310# Example projects
1311
1312There is one [working example](examples) of how this package can be used in a simple 'hello world' serverless application:
1313
13141. [Using the Serverless Framework and TypeScript](examples/ts)