0.5.0 • Published 2 years ago

@compeon-os/lambda-helpers v0.5.0

Weekly downloads
-
License
ISC
Repository
-
Last release
2 years ago

COMPEON Lambda Helpers

Useful functions that will probably be used across all Lambda functions. Mainly by using the wrapper compeonHandler and log.

Note: Type definitions in this README are meant as a help and do not strictly follow TypeScript or Flow conventions.

compeonHandler

compeonHandler(
  handler: func,
  middleware?: func[]
): Promise<AWS response>`

params:
  handler: (event, context, callback) => { body, statusCode, headers }
  middleware: ({ event, context, callback }) => ({ body, statusCode, headers })

The main function of this package. It...

  • expects an event handler, sync or async
  • passes your result to the AWS lambda_handler
  • catches and logs (see log) errors thrown inside your event handler
  • accepts middleware to modify incoming parameters and outgoing responses alike (note the single object with keys event, context, and callback)

Note: requires Node 8.10 or above

Example:

exports.lambda_handler = compeonHandler(event =>
  const { testString } = event.queryStringParameters

  // the following gets caught and logged
  if (!testString) throw new CompeonError('I need a test string', 422)

  // this works as well, but error stack would be missing:
  if (!testString) throw 'I need a test string'

  // the following gets returned as promise
  return {
    body: 'my result'
  }
)

The response, in error case, would be:

{
  "body": {
    "errors": [{
      "code": "Unprocessible Entity",
      "detail": "I need a testString",
      "status": 422,
      "title": "422 (Unprocessable Entity)"
    }]
  },
  "statusCode": 422,
}

Note that this example uses the CompeonError (see CompeonError) to set a status.

CompeonError

Error class that allows saving status (as well as the message). Example

if (errorCondition) throw new CompeonError('I need a test string', 422)

If thrown inside a compeonHandler, the error gets logged and passed as the lambda's response. Note that error responses, just like normal ones, pass through the outputMiddleware (see using middleware).

using middleware

The compeonHandler accepts an array of middleware functions as an optional second parameter.

A middleware needs to adhere to the following structure

const middleware = next => async ({ event, context, callback }) => {
  // Do something with inputs.

  // Call next middleware. As the lambda handler is async, any middleware
  // that deals with a response must be async as well.
  const response = await next({ event, context, callback })

  // Do something with the response.

  // Pass response to the preceding middleware or, if the last one, back to AWS.
  return response
}

ready-to-use middleware

This package provides some useful outputMiddleware for convenience.

awsResponseMiddleware

Transforms any object to an AWS response by wrapping your result into the key body. Example:

exports.lambda_handler = compeonHandler(
  // your handler
  event => ({ test: event.inputKey, resultKey: 'resultValue' }),
  [
    awsResponseMiddleware,
    // add a  in inputMiddleware for demonstration purposes
    next => ({ event }) => ({ event: { inputKey: 'from inputMiddleware' } })
  ]
)

This would result in the following response:

{
  "body": {
    "test": "from inputMiddleware",
    "resultKey": "resultValue"
  },
  "statusCode": 200
}

corsMiddleware

Factory function for a middleware that answers early to OPTIONS requests and adds CORS headers to responses.

Example:

const cors = corsMiddleware({
  // Must be true if the middleware is used in a CloudFront lambda.
  // Default is false.
  cloudFront: false,

  // Custom headers to be added to response.
  customHeaders: { 'Access-Control-Allow-Headers': 'authorization, content-type, content-disposition' },

  // Whitelisted HTTP methods.
  // Default is [].
  methods: ['OPTIONS', 'POST', 'PUT'],

  // Whitelisted origins.
  // Default is [].
  origins: ['http://localhost:3001', 'http://localhost:3002']
})

exports.lambda_handler = compeonHandler(event => { ... }, [cors])

To allow (= mirror) all origins, add '**' to the origin whitelist. This precedes any other whitelist configuration.

caseTransformationMiddleware

Factory function for a middleware that camelcases the event body of the request and dasherizes the response body.

Example:

exports.lambda_handler = compeonHandler(
  // Your handler
  event => ({ resultKey: `${event.resultKey} ABC` })

  // Camelcase input and dasherize output
  [caseTransformationMiddleware(), awsResponseMiddleware],
)

Given the AWS event

{
  "body": {
    "result-key": "test"
  }
}

the response of the lambda would be

{
  "body": {
    "result-key": "test ABC"
  },
  "statusCode": 200
}

Options

caseTransformationMiddleware({
  // Specifies whether the request body should be camelcased.
  // Default is true.
  transformRequest,

  // Specifies whether the response body should be camelcased.
  // Default is true.
  transformResponse
})

Logging

This section covers functions for logging functionality.

log

`log(data: any): void`

Log data in a JSON-stringified, formatted way. Use it the same way you would use console.log, but note that calls with multiple arguments are not supported.

Example:

log(myTestObject)

toJsonString

`toJsonString(data: Object): void`

Just a convenience method. Calls JSON.stringify(data, null, 2).

Deep Object transformation

This package also includes three functions to transform object keys: for kebab-case, camelCase, and a generic one with custom transformator.

deepCamelTransform

deepCamelTransform(data: any): any

Deeply transforms object keys of data to camelCase keys. Also transforms arrays if they contain any objects. Example:

deepCamelTransform(
  { PascalCaseKey: 'value', nested_object: { 'kebab-case': 'value' } }
)
// { pascalCaseKey: 'value', nestedObject: { kebabCase: 'value' } }

deepKebabTransform

deepKebabTransform(data: any): any

Same as deepCamelTransform, but with kebab-case: Example:

deepKebabTransform(
  { PascalCaseKey: 'value', nested_object: { 'kebab-case': 'value' } }
)
// { "pascal-case-key": 'value', "nested-object": { "kebab-case": 'value' } }

deepKeyTransformer

deepKeyTransformer(transformFunc: (string) => (string)): (Array|Object) => (Array|Object)

Creates a deepKeyTransformer with the given transformer. Example:

deepKeyTransformer(kebabCase)
// returns a function equal to deepKebabTransform

responses

The following functions turn some input into AWS Lambda responses of the form:

{
  "body": someBody,
  "statusCode": number,
  "headers": optionalHeaders
}

Transforms that can directly be used on axios responses are provided as well.

handleAxiosError

handleAxiosError(axiosResponse: Object): Object

Mostly used inside catch of an axios request. Extracts error from the response and calls handleError with it. Note: Will only work with responses that follow the structure of an axios response.

Example:

axios.get(apiUrl)
  .then(someResponseHandler)
  .catch(handleAxiosError)

handleAxiosOk

handleAxiosOk(body: Object): Object

Can be used to chain the data of an axios request directly as the Lambda response. Expects a data attribute

exports.lambda_handler = (event) => {
  return axios.get('https://jsonplaceholder.typicode.com/todos/1')
    .then(handleAxiosOk)
}

// the lambda returns {"userId":1,"id":1,"title":"delectus aut autem","completed":false}

handleEmptyOk

handleEmptyOk(): Object

If an empty 200 response is wanted (Still contains the string 'ok').

exports.lambda_handler = (event) => {
  // omitted ...

  return axios.get(apiUrl)
    .then(someResponseHandler) // Can provide anything, handleEmptyOk has no expectations
    .then(handleEmptyOk)
}

handleError

handleError(error: any, status?: number): Object

Used to log the provided error to cloudwatch. Returns AWS Lambda conform response object. If error is not a string, it gets JSON-stringified. status defaults to 500.

Example:

handleError('not found', 404)
// => logs 'not found' to cloudwatch console
// => { body: 'not found', statusCode: 404 }

handleOk

handleOk(body?: string|object, headers?: object, statusCode?: number): Object

Returns an AWS Lambda conform response object with the preferred body. statusCode defaults to 200. Can be used to chain a previous response to the Lambda output.

Example:

handleOk()
// => { "body": "ok", "statusCode": 200 }


// May also be used as a last handler in a promise chain to directly transform the previous' handlers response as the response of the Lambda itself:
exports.lambda_handler = (event) => {
  return axios.get(apiUrl)
    .then(someResponseHandler) // => Must provide json-parsable object
    .then(handleOk)
}

handleRedirect

handleRedirect(url: string, statusCode?: 302): Object

Can be used to quickly generate HTTP redirections. Example:

handleRedirect('www.example.com')
// => { "headers": { "Location": "www.example.com" }, "statusCode": 322 }