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 |
|
30 | 1. [Overview](#overview)
|
31 | 1. [Installation and setup](#installation-and-setup)
|
32 | 1. [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)
|
40 | 1. [Example projects](#example-projects)
|
41 |
|
42 | # Overview
|
43 |
|
44 | ### TL;DR
|
45 |
|
46 | 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.
|
47 |
|
48 | ### Rationale and motivation
|
49 |
|
50 | AWS 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 |
|
56 | Install and save the package:
|
57 |
|
58 | `npm i -S @manwaring/lambda-wrapper`
|
59 |
|
60 | `yarn add @manwaring/lambda-wrapper`
|
61 |
|
62 | ## Optional configuration
|
63 |
|
64 | If you want the wrapper to log request and response messages (helpful for debugging) set an environemnt variable for `LAMBDA_WRAPPER_LOG=true`.
|
65 |
|
66 | If 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 |
|
70 | Each event listed here has a wrapper which provides a deconstructable method signature exposing parsed/unmarshalled request parameters and helper response methods.
|
71 |
|
72 | 1. [API Gateway](#api-gateway)
|
73 | 1. [API Gateway HTTP API](#api-gateway-http-api)
|
74 | 1. [CloudFormation Custom Resource](#cloudformation-custom-resource)
|
75 | 1. [DynamoDB Stream](#dynamodb-stream)
|
76 | 1. [Lambda Authorizer](#lambda-authorizer)
|
77 | 1. [SNS](#sns)
|
78 | 1. [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
|
85 | import { api } from '@manwaring/lambda-wrapper';
|
86 | import { CustomInterface } from './custom-interface';
|
87 | import { doSomething } from './you-code';
|
88 |
|
89 | export 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 |
|
103 | By 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
|
109 | import { api } from '@manwaring/lambda-wrapper';
|
110 | import { doSomething } from './you-code';
|
111 |
|
112 | export 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 |
|
132 | Note that all properties are undefined if not present on the original request.
|
133 |
|
134 | ```ts
|
135 | export 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
|
160 | interface 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
|
173 | interface 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
|
188 | interface 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
|
202 | interface 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
|
218 | interface 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
|
234 | export 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
|
291 | const response = { hello: 'world' };
|
292 | return 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
|
309 | const response = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
|
310 | const headers = { 'Content-Type': 'image/svg+xml' };
|
311 | return 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
|
351 | return 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
|
366 | const response = { invalid: 'properties' };
|
367 | return 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
|
407 | return 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
|
422 | const response = 'Not found';
|
423 | return 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
|
462 | return 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
|
477 | const response = 'Not authorized';
|
478 | return 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
|
515 | const url = 'https://github.com/manwaring/lambda-wrapper';
|
516 | return 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
|
532 | const url = 'https://github.com/manwaring/lambda-wrapper';
|
533 | return 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
|
575 | return 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
|
590 | catch (err) {
|
591 | const body = { error: 'Unexpected error' };
|
592 | return error({ body, err });
|
593 | }
|
594 |
|
595 | // logs
|
596 | console.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
|
639 | return 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
|
654 | const body = { message: 'Custom response' };
|
655 | return 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 |
|
672 | Other 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
|
677 | import { httpApi } from '@manwaring/lambda-wrapper';
|
678 | import { CustomInterface } from './custom-interface';
|
679 | import { doSomething } from './you-code';
|
680 |
|
681 | export 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 |
|
695 | By 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
|
701 | import { httpApi } from '@manwaring/lambda-wrapper';
|
702 | import { doSomething } from './you-code';
|
703 |
|
704 | export 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 |
|
724 | Note that all properties are undefined if not present on the original request.
|
725 |
|
726 | ```ts
|
727 | export 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
|
753 | interface 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
|
766 | interface 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
|
781 | interface 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
|
795 | interface 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
|
811 | interface 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
|
829 | export 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
|
900 | const response = { hello: 'world' };
|
901 | return 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
|
918 | const response = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
|
919 | const headers = { 'Content-Type': 'image/svg+xml' };
|
920 | return 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
|
960 | return 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
|
975 | const response = { invalid: 'properties' };
|
976 | return 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
|
1016 | return 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
|
1031 | const response = 'Not found';
|
1032 | return 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
|
1071 | return 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
|
1086 | const response = 'Not authorized';
|
1087 | return 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
|
1124 | const url = 'https://github.com/manwaring/lambda-wrapper';
|
1125 | return 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
|
1141 | const url = 'https://github.com/manwaring/lambda-wrapper';
|
1142 | return 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
|
1184 | return 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
|
1199 | catch (err) {
|
1200 | const body = { error: 'Unexpected error' };
|
1201 | return error({ body, err });
|
1202 | }
|
1203 |
|
1204 | // logs
|
1205 | console.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
|
1248 | return 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
|
1263 | const body = { message: 'Custom response' };
|
1264 | return 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
|
1284 | import { cloudFormation } from '@manwaring/lambda-wrapper';
|
1285 |
|
1286 | export 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
|
1301 | interface 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
|
1313 | import { dynamodbStream } from '@manwaring/lambda-wrapper';
|
1314 | import { 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)
|
1317 | export 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 |
|
1326 | interface CustomInterface {
|
1327 | id: number;
|
1328 | value: string;
|
1329 | }
|
1330 | ```
|
1331 |
|
1332 | ## Properties and methods available on wrapper signature
|
1333 |
|
1334 | ```ts
|
1335 | interface 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 |
|
1344 | interface 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
|
1359 | import { authorizer } from '@manwaring/lambda-wrapper';
|
1360 | const verifier = new Verifier(); // setup and configure JWT validation library
|
1361 |
|
1362 | export 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
|
1378 | interface 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 |
|
1386 | interface 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
|
1404 | import { sns } from '@manwaring/lambda-wrapper';
|
1405 | import { 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)
|
1408 | export 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
|
1421 | interface 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
|
1434 | import { wrapper } from '@manwaring/lambda-wrapper';
|
1435 | import { 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)
|
1438 | export 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
|
1452 | interface 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 |
|
1461 | There is one [working example](examples) of how this package can be used in a simple 'hello world' serverless application:
|
1462 |
|
1463 | 1. [Using the Serverless Framework and TypeScript](examples/ts)
|