npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@manwaring/lambda-wrapper

v4.4.4

Published

A lambda handler wrapper to abstract common functionality and provide useful defaults

Downloads

732

Readme

AWS Lambda wrapper library

This documentation is for v4 of the library - go here for v1, v2, and v3 documentation.

  1. Overview
  2. Installation and setup
  3. Supported events
  4. Example projects

Overview

TL;DR

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.

Rationale and motivation

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.

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.

Installation and setup

Install and save the package:

npm i -S @manwaring/lambda-wrapper

yarn add @manwaring/lambda-wrapper

Optional configuration

If you want the wrapper to log request and response messages (helpful for debugging) set an environemnt variable for LAMBDA_WRAPPER_LOG=true.

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 and serverless-plugin-git-variables for libraries that can help you set git revision automatically.

Supported events

Each event listed here has a wrapper which provides a deconstructable method signature exposing parsed/unmarshalled request parameters and helper response methods.

  1. API Gateway
  2. API Gateway HTTP API
  3. CloudFormation Custom Resource
  4. DynamoDB Stream
  5. Lambda Authorizer
  6. SNS
  7. Generic event (a basic wrapper with support for success and failure responses)

API Gateway

Sample TypeScript implementation

import { api } from '@manwaring/lambda-wrapper';
import { CustomInterface } from './custom-interface';
import { doSomething } from './you-code';

export const handler = api<CustomInterface>(async ({ body, path, success, invalid, error }) => {
  try {
    const { pathParam1, pathParam2 } = path;
    if (!pathParam1) {
      return invalid();
    }
    const results = await doSomething(body, pathParam1, pathParam2);
    return success({ body: results });
  } catch (err) {
    return error({ err });
  }
});

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.

import { api } from '@manwaring/lambda-wrapper';
import { doSomething } from './you-code';

export const handler = api(async ({ body, path, success, invalid, error }) => {
  try {
    const { pathParam1, pathParam2 } = path;
    if (!pathParam1) {
      return invalid();
    }
    const results = await doSomething(body, pathParam1, pathParam2);
    return success({ body: results });
  } catch (err) {
    return error({ err });
  }
});

Properties and methods available on wrapper signature

Note that all properties are undefined if not present on the original request.

export interface ApiSignature<T = any> {
  event: APIGatewayEvent; // original event provided by AWS
  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`)
  websocket: WebsocketRequest; // websocket connection payload
  path: { [name: string]: string }; // path params as key-value pairs
  query: { [name: string]: string }; // query params as key-value pairs
  headers: { [name: string]: string }; // headers as key-value pairs
  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)
  auth: any; // auth context from custom authorizer
  success(params?: ResponseParameters): ApiResponse;
  invalid(params?: ResponseParameters): ApiResponse;
  notFound(params?: ResponseParameters): ApiResponse;
  notAuthorized(params?: ResponseParameters): ApiResponse;
  redirect(params: RedirectParameters): ApiResponse;
  error(params?: ErrorParameters): ApiResponse;
  custom(params: CustomParameters): ApiResponse;
}
interface ApiResponse {
  statusCode: number;
  headers: { [name: string]: any };
  body?: string;
}
interface ResponseParameters {
  body?: any; // response body
  json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode?: number; // status code to return, defaults by callback (success: 200, invalid: 400, notFound: 404, notAuthorized: 401, redirect: 302, error: 500)
  headers?: { [key: string]: any }; // custom headers to include
}
interface RedirectParameters {
  url: string; // url to redirect to
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode?: number; // status code to return, defaults to 302
  headers?: { [key: string]: any }; // custom headers to include
}
interface ErrorParameters {
  body?: any; // response body
  json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode?: number; // status code to return, defaults to 500
  headers?: { [key: string]: any }; // custom headers to include
  err?: Error; // optional Error object for automatic logging
}
interface CustomParameters {
  body?: any; // response body
  json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode: number; // status code to return
  headers?: { [key: string]: any }; // custom headers to include
  err?: Error; // optional Error object for automatic logging
}
export interface WebsocketRequest {
  accountId: string;
  apiId: string;
  connectedAt?: number;
  connectionId?: string;
  domainName?: string;
  domainPrefix?: string;
  eventType?: string;
  extendedRequestId?: string;
  protocol: string;
  httpMethod: string;
  identity: APIGatewayEventIdentity;
  messageDirection?: string;
  messageId?: string | null;
  path: string;
  stage: string;
  requestId: string;
  requestTime?: string;
  requestTimeEpoch: number;
  resourceId: string;
  resourcePath: string;
  routeKey?: string;
}

Response functions

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 200
}

Invocation with defaults

const response = { hello: 'world' };
return success({ body: response });

// returns
{
  body: "{\"hello\":\"world\"}",
  statusCode: 200,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
    'Content-Type': 'application/json'
  }
}

Invocation overriding defaults

const response = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
const headers = { 'Content-Type': 'image/svg+xml' };
return success({ body: response, json: false, cors: false, headers });

// returns
{
  body: "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>",
  statusCode: 200,
  headers: { 'Content-Type': 'image/svg+xml' }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 400
}

Invocation with defaults

return invalid();

// returns
{
  statusCode: 400,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const response = { invalid: 'properties' };
return invalid({ body: response, cors: false });

// returns
{
  body: "{\"invalid\":\"properties\"}",
  statusCode: 400,
  headers: { 'Content-Type': 'application/json' }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 404
}

Invocation with defaults

return notFound();

// returns
{
  statusCode: 404,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const response = 'Not found';
return notFound({ body: response, cors: false });

// returns
{
  body: "Not found",
  statusCode: 404,
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 401
}

Invocation with defaults

return notAuthorized();

// returns
{
  statusCode: 401,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const response = 'Not authorized';
return notAuthorized({ body: response, cors: false });

// returns
{
  body: "Not Authorized",
  statusCode: 401,
}

Available parameters

{
  url: string,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  cors: true,
  statusCode: 302
}

Invocation with defaults

const url = 'https://github.com/manwaring/lambda-wrapper';
return redirect({ url });

// returns
{
  statusCode: 302,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
    'Location': 'https://github.com/manwaring/lambda-wrapper'
  }
}

Invocation overriding defaults

const url = 'https://github.com/manwaring/lambda-wrapper';
return redirect({ url, statusCode: 308, cors: false });

// returns
{
  statusCode: 308,
  headers: {
    'Location': 'https://github.com/manwaring/lambda-wrapper'
  }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any},
  err?: Error
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 500
}

Invocation with defaults

return error();

// returns
{
  statusCode: 500,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

catch (err) {
  const body = { error: 'Unexpected error' };
  return error({ body, err });
}

// logs
console.debug(err);

// returns
{
  body: "{\"error\": \"Unexpected error\"}",
  statusCode: 500,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode: number,
  headers?: { [key: string]: any},
  err?: Error
}

Default parameters

{
  json: true,
  cors: true,
}

Invocation with defaults

return custom({ statusCode: 418 });

// returns
{
  statusCode: 418,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const body = { message: 'Custom response' };
return custom({ body, statusCode: 418 });

// returns
{
  body: "{\"message\": \"Custom response\"}",
  statusCode: 418,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

API Gateway HTTP API

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.

Sample TypeScript implementation

import { httpApi } from '@manwaring/lambda-wrapper';
import { CustomInterface } from './custom-interface';
import { doSomething } from './you-code';

export const handler = httpApi<CustomInterface>(async ({ body, path, success, invalid, error }) => {
  try {
    const { pathParam1, pathParam2 } = path;
    if (!pathParam1) {
      return invalid();
    }
    const results = await doSomething(body, pathParam1, pathParam2);
    return success({ body: results });
  } catch (err) {
    return error({ err });
  }
});

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.

import { httpApi } from '@manwaring/lambda-wrapper';
import { doSomething } from './you-code';

export const handler = httpApi(async ({ body, path, success, invalid, error }) => {
  try {
    const { pathParam1, pathParam2 } = path;
    if (!pathParam1) {
      return invalid();
    }
    const results = await doSomething(body, pathParam1, pathParam2);
    return success({ body: results });
  } catch (err) {
    return error({ err });
  }
});

Properties and methods available on wrapper signature

Note that all properties are undefined if not present on the original request.

export interface HttpApiSignature<T = any> {
  event: HttpApiEvent; // original event provided by AWS
  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`)
  rawPath: string; // the endpoint path used to invoke this Lambda
  path: { [name: string]: string }; // path params as key-value pairs
  query: { [name: string]: string }; // query params as key-value pairs
  rawQueryString: string // the raw query string from the request
  headers: { [name: string]: string }; // headers as key-value pairs
  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)
  auth: any; // auth context from JWT authorizer
  success(params?: ResponseParameters): ApiResponse;
  invalid(params?: ResponseParameters): ApiResponse;
  notFound(params?: ResponseParameters): ApiResponse;
  notAuthorized(params?: ResponseParameters): ApiResponse;
  redirect(params: RedirectParameters): ApiResponse;
  error(params?: ErrorParameters): ApiResponse;
  custom(params: CustomParameters): ApiResponse;
}
interface ApiResponse {
  statusCode: number;
  headers: { [name: string]: any };
  body?: string;
}
interface ResponseParameters {
  body?: any; // response body
  json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode?: number; // status code to return, defaults by callback (success: 200, invalid: 400, notFound: 404, notAuthorized: 401, redirect: 302, error: 500)
  headers?: { [key: string]: any }; // custom headers to include
}
interface RedirectParameters {
  url: string; // url to redirect to
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode?: number; // status code to return, defaults to 302
  headers?: { [key: string]: any }; // custom headers to include
}
interface ErrorParameters {
  body?: any; // response body
  json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode?: number; // status code to return, defaults to 500
  headers?: { [key: string]: any }; // custom headers to include
  err?: Error; // optional Error object for automatic logging
}
interface CustomParameters {
  body?: any; // response body
  json?: boolean; // indicates if body should be JSON-stringified and content-type header set to application/json, defaults to true
  cors?: boolean; // indicates if CORS headers should be added, defaults to true
  statusCode: number; // status code to return
  headers?: { [key: string]: any }; // custom headers to include
  err?: Error; // optional Error object for automatic logging
}

AWS documentation of raw event

export interface HttpApiEvent {
  version: string;
  routeKey: string;
  rawPath: string;
  rawQueryString: string;
  cookies: string[];
  headers: { [key: string]: string };
  queryStringParameters: { [key: string]: string };
  requestContext: {
    accountId: string;
    apiId: string;
    authorizer: {
      jwt: {
        claims: { [key: string]: string };
        scopes: string[];
      };
    };
    domainName: string;
    domainPrefix: string;
    http: {
      method: string;
      path: string;
      protocol: string;
      sourceIp: string;
      userAgent: string;
    };
    requestId: string;
    routeKey: string;
    stage: string;
    time: string;
    timeEpoch: number;
  };
  body: string;
  pathParameters: { [key: string]: string };
  isBase64Encoded: boolean;
  stageVariables: { [key: string]: string };
}

Response functions

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 200
}

Invocation with defaults

const response = { hello: 'world' };
return success({ body: response });

// returns
{
  body: "{\"hello\":\"world\"}",
  statusCode: 200,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
    'Content-Type': 'application/json'
  }
}

Invocation overriding defaults

const response = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
const headers = { 'Content-Type': 'image/svg+xml' };
return success({ body: response, json: false, cors: false, headers });

// returns
{
  body: "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>",
  statusCode: 200,
  headers: { 'Content-Type': 'image/svg+xml' }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 400
}

Invocation with defaults

return invalid();

// returns
{
  statusCode: 400,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const response = { invalid: 'properties' };
return invalid({ body: response, cors: false });

// returns
{
  body: "{\"invalid\":\"properties\"}",
  statusCode: 400,
  headers: { 'Content-Type': 'application/json' }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 404
}

Invocation with defaults

return notFound();

// returns
{
  statusCode: 404,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const response = 'Not found';
return notFound({ body: response, cors: false });

// returns
{
  body: "Not found",
  statusCode: 404,
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 401
}

Invocation with defaults

return notAuthorized();

// returns
{
  statusCode: 401,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const response = 'Not authorized';
return notAuthorized({ body: response, cors: false });

// returns
{
  body: "Not Authorized",
  statusCode: 401,
}

Available parameters

{
  url: string,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any}
}

Default parameters

{
  cors: true,
  statusCode: 302
}

Invocation with defaults

const url = 'https://github.com/manwaring/lambda-wrapper';
return redirect({ url });

// returns
{
  statusCode: 302,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
    'Location': 'https://github.com/manwaring/lambda-wrapper'
  }
}

Invocation overriding defaults

const url = 'https://github.com/manwaring/lambda-wrapper';
return redirect({ url, statusCode: 308, cors: false });

// returns
{
  statusCode: 308,
  headers: {
    'Location': 'https://github.com/manwaring/lambda-wrapper'
  }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode?: number,
  headers?: { [key: string]: any},
  err?: Error;
}

Default parameters

{
  json: true,
  cors: true,
  statusCode: 500
}

Invocation with defaults

return error();

// returns
{
  statusCode: 500,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

catch (err) {
  const body = { error: 'Unexpected error' };
  return error({ body, err });
}

// logs
console.debug(err);

// returns
{
  body: "{\"error\": \"Unexpected error\"}",
  statusCode: 500,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Available parameters

{
  body?: any,
  json?: boolean,
  cors?: boolean,
  statusCode: number,
  headers?: { [key: string]: any},
  err?: Error
}

Default parameters

{
  json: true,
  cors: true,
}

Invocation with defaults

return custom({ statusCode: 418 });

// returns
{
  statusCode: 418,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

Invocation overriding defaults

const body = { message: 'Custom response' };
return custom({ body, statusCode: 418 });

// returns
{
  body: "{\"message\": \"Custom response\"}",
  statusCode: 418,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
  }
}

CloudFormation Custom Resource

Sample TypeScript implementation

import { cloudFormation } from '@manwaring/lambda-wrapper';

export const handler = cloudFormation(({ event, success, failure }) => {
  try {
    const { BucketName } = event.ResourceProperties;
    return success();
  } catch (err) {
    return failure(err);
  }
});

*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

Properties and methods available on wrapper signature

interface CloudFormationSignature {
  event: CloudFormationCustomResourceEvent; // original event
  success(payload?: any): void; // sends CloudFormation success event
  failure(message?: any): void; // sends CloudFormation failure event
}

DynamoDB Stream

Sample TypeScript implementation

import { dynamodbStream } from '@manwaring/lambda-wrapper';
import { CustomInterface } from './custom-interface';

// 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)
export const handler = dynamodbStream<CustomInterface>(async ({ newVersions, success, error }) => {
  try {
    newVersions.forEach((version) => console.log(version));
    return success(newVersions);
  } catch (err) {
    return error(err);
  }
});

interface CustomInterface {
  id: number;
  value: string;
}

Properties and methods available on wrapper signature

interface DynamoDBStreamSignature<T> {
  event: DynamoDBStreamEvent; // original event
  newVersions: T[]; // array of all unmarshalled javascript objects of new images
  oldVersions: T[]; // array of all unmarshalled javascript objects of old images
  versions: Version<T>[]; // array of full version object (new image, old image, etc - see Version interface)
  success(message?: any): any; // logs and returns the message
  error(error?: any): void; // logs the error and throws it
}

interface Version<T> {
  newVersion: T; // unmarshalled javascript object of new image (if exists) or null
  oldVersion: T; // unmarshalled javascript object of old image (if exists) or null
  keys: any; // unmarshalled javascript object of keys (includes key values)
  tableName: string; // name of the table the object came from
  tableArn: string; // arn of the table the object came from
  eventName: 'INSERT' | 'MODIFY' | 'REMOVE'; // name of the event (INSERT || MODIFY || REMOVE)
}

Lambda Authorizer

Sample TypeScript implementation

import { authorizer } from '@manwaring/lambda-wrapper';
const verifier = new Verifier(); // setup and configure JWT validation library

export const handler = authorizer(async ({ token, valid, invalid }) => {
  try {
    if (!token) {
      return invalid('Missing token');
    }
    const jwt = await verifier.verifyAccessToken(token);
    return valid(jwt);
  } catch (err) {
    return invalid(err);
  }
});

Properties and methods available on wrapper signature

interface AuthorizerSignature {
  event: CustomAuthorizerEvent; // original event
  token: string; // authorizer token from original event
  valid(jwt: any): Policy; // returns AWS policy to authenticate request, and adds auth context if available
  invalid(message?: any): void; // records invalid information and throws 401 unauthorized
  error(error?: any): void; // records error information and throws 401 unauthorized
}

interface Policy {
  principalId: string;
  policyDocument: {
    Version: string;
    Statement: {
      Action: string;
      Effect: string;
      Resource: string;
    }[];
  };
}

SNS

Sample TypeScript implementation

import { sns } from '@manwaring/lambda-wrapper';
import { CustomInterface } from './custom-interface';

// 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)
export const handler = sns<CustomInterface>(async ({ message, success, error }) => {
  try {
    console.log(message);
    return success();
  } catch (err) {
    return error(err);
  }
});

Properties and methods available on wrapper signature

interface SnsSignature {
  event: SNSEvent; // original event
  message: any; // JSON-parsed message from event
  success(message?: any): any; // logs and returns the message
  error(error?: any): void; // logs the error and throws
}

Generic event

Sample TypeScript implementation

import { wrapper } from '@manwaring/lambda-wrapper';
import { CustomInterface } from './custom-interface';

// 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)
export const handler = wrapper<CustomInterface>(async ({ event, success, error }) => {
  try {
    const { value1, value2 } = event;
    const results = await doSomething(value1, value2);
    return success(results);
  } catch (err) {
    return error(err);
  }
});

Properties and methods available on wrapper signature

interface WrapperSignature<T> {
  event: T; // original event
  success(message?: any): any; // logs and returns the message
  error(error?: any): void; // logs the error and throws
}

Example projects

There is one working example of how this package can be used in a simple 'hello world' serverless application:

  1. Using the Serverless Framework and TypeScript