UNPKG

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