UNPKG

@sailplane/lambda-utils

Version:

Use middleware to remove redundancy in AWS Lambda handlers.

1 lines 11.6 kB
{"version":3,"sources":["../lib/handler-utils.ts","../lib/resolved-promise-is-success.ts","../lib/unhandled-exception.ts","../lib/logger-context.ts"],"sourcesContent":["import { APIGatewayProxyResult } from \"aws-lambda\";\nimport middy from \"@middy/core\";\nimport cors from \"@middy/http-cors\";\nimport httpEventNormalizer from \"@middy/http-event-normalizer\";\nimport httpHeaderNormalizer from \"@middy/http-header-normalizer\";\nimport httpJsonBodyParser from \"@middy/http-json-body-parser\";\nimport { Logger } from \"@sailplane/logger\";\nimport {\n AsyncMiddyifedHandlerV1,\n AsyncMiddyifedHandlerV2,\n AsyncProxyHandlerV1,\n AsyncProxyHandlerV2,\n} from \"./types\";\nimport { resolvedPromiseIsSuccessMiddleware } from \"./resolved-promise-is-success\";\nimport { unhandledExceptionMiddleware } from \"./unhandled-exception\";\nimport { loggerContextMiddleware } from \"./logger-context\";\n\nconst logger = new Logger(\"lambda-utils\");\n\n/**\n * Wrap an API Gateway V1 format proxy lambda function handler to add features:\n * - Set CORS headers.\n * - Normalize incoming headers to lowercase\n * - If incoming content is JSON text, replace event.body with parsed object.\n * - Ensures that event.queryStringParameters and event.pathParameters are defined,\n * to avoid TypeErrors.\n * - Ensures that handler response is formatted properly as a successful\n * API Gateway result.\n * - Catch http-errors exceptions into proper HTTP responses.\n * - Catch other exceptions and return as HTTP 500\n * - Set Lambda invocation and API request context in @sailplane/logger\n *\n * This wrapper includes commonly useful middleware. You may further wrap it\n * with your own function that adds additional middleware, or just use it as\n * an example.\n *\n * @param handler async function to wrap\n * @see https://middy.js.org/#:~:text=available%20middlewares\n * @see https://www.npmjs.com/package/http-errors\n */\nexport function wrapApiHandler(handler: AsyncProxyHandlerV1): AsyncMiddyifedHandlerV1 {\n return middy(handler)\n .use(cors({ origin: \"*\" }))\n .use(resolvedPromiseIsSuccessMiddleware())\n .use(unhandledExceptionMiddleware())\n .use(loggerContextMiddleware())\n .use(httpEventNormalizer())\n .use(httpHeaderNormalizer())\n .use(\n // Parse JSON body, but don't throw when there is no body\n httpJsonBodyParser({ disableContentTypeError: true }),\n ) as unknown as AsyncMiddyifedHandlerV1;\n}\nexport const wrapApiHandlerV1 = wrapApiHandler;\n\n/**\n * Wrap an API Gateway V2 format proxy lambda function handler to add features:\n * - Set CORS headers.\n * - Normalize incoming headers to lowercase\n * - If incoming content is JSON text, replace event.body with parsed object.\n * - Ensures that event.queryStringParameters and event.pathParameters are defined,\n * to avoid TypeErrors.\n * - Ensures that handler response is formatted properly as a successful\n * API Gateway result.\n * - Catch http-errors exceptions into proper HTTP responses.\n * - Catch other exceptions and return as HTTP 500\n * - Set Lambda invocation and API request context in @sailplane/logger\n *\n * This wrapper includes commonly useful middleware. You may further wrap it\n * with your own function that adds additional middleware, or just use it as\n * an example.\n *\n * @param handler async function to wrap\n * @see https://middy.js.org/#:~:text=available%20middlewares\n * @see https://www.npmjs.com/package/http-errors\n */\nexport function wrapApiHandlerV2(handler: AsyncProxyHandlerV2): AsyncMiddyifedHandlerV2 {\n return middy(handler)\n .use(cors({ origin: \"*\" }))\n .use(resolvedPromiseIsSuccessMiddleware())\n .use(unhandledExceptionMiddleware())\n .use(loggerContextMiddleware())\n .use(httpEventNormalizer())\n .use(httpHeaderNormalizer())\n .use(\n // Parse JSON body, but don't throw when there is no body\n httpJsonBodyParser({ disableContentTypeError: true }),\n ) as unknown as AsyncMiddyifedHandlerV2;\n}\n\n/**\n * Construct the object that API Gateway payload format v1 wants back\n * upon a successful run. (HTTP 200 Ok)\n *\n * This normally is not needed. If the response is simply the content to return as the\n * body of the HTTP response, you may simply return it from the handler given to\n * #wrapApiHandler(handler). It will automatically transform the result.\n *\n * @param result object to serialize into JSON as the response body\n * @returns {APIGatewayProxyResult}\n */\nexport function apiSuccess(result?: any): APIGatewayProxyResult {\n return {\n statusCode: 200,\n body: result ? JSON.stringify(result) : \"\",\n headers: {\n \"content-type\": result ? \"application/json; charset=utf-8\" : \"text/plain; charset=utf-8\",\n },\n };\n}\n\n/**\n * Construct the object that API Gateway payload format v1 wants back upon a failed run.\n *\n * Often, it is simpler to throw a http-errors exception from your #wrapApiHandler\n * handler.\n *\n * @see https://www.npmjs.com/package/http-errors\n * @param statusCode HTTP status code, between 400 and 599.\n * @param message string to return in the response body\n * @returns {APIGatewayProxyResult}\n */\nexport function apiFailure(statusCode: number, message?: string): APIGatewayProxyResult {\n const response = {\n statusCode,\n body: message || \"\",\n headers: {\n \"content-type\": \"text/plain; charset=utf-8\",\n },\n };\n\n logger.warn(\"Response to API Gateway: \", response);\n return response;\n}\n","import middy from \"@middy/core\";\nimport { APIGatewayProxyEventAnyVersion, APIGatewayProxyResultAnyVersion } from \"./types\";\n\n/**\n * Middleware to allow an async handler to return its exact response body.\n * This middleware will wrap it up as an APIGatewayProxyResult.\n * Must be registered as the last (thus first to run) \"after\" middleware.\n */\nexport const resolvedPromiseIsSuccessMiddleware = (): middy.MiddlewareObj<\n APIGatewayProxyEventAnyVersion,\n APIGatewayProxyResultAnyVersion\n> => ({\n after: async (request) => {\n // If response isn't a proper API result object, convert it into one.\n const response = request.response;\n if (!response || typeof response !== \"object\" || (!response.statusCode && !response.body)) {\n request.response = {\n statusCode: 200,\n body: response ? JSON.stringify(response) : \"\",\n headers: {\n \"content-type\": response\n ? \"application/json; charset=utf-8\"\n : \"text/plain; charset=utf-8\",\n },\n };\n }\n },\n});\n","import middy from \"@middy/core\";\nimport { APIGatewayProxyEventAnyVersion, APIGatewayProxyResultAnyVersion } from \"./types\";\nimport { Logger } from \"@sailplane/logger\";\n\nconst logger = new Logger(\"lambda-utils\");\n\n/**\n * Middleware to handle any otherwise unhandled exception by logging it and generating\n * an HTTP 500 response.\n *\n * Fine-tuned to work better than the Middy version, and uses @sailplane/logger.\n */\nexport const unhandledExceptionMiddleware = (): middy.MiddlewareObj<\n APIGatewayProxyEventAnyVersion,\n APIGatewayProxyResultAnyVersion\n> => ({\n onError: async (request) => {\n logger.error(\"Unhandled exception:\", request.error);\n\n request.response = request.response || {};\n /* istanbul ignore else - nominal path is for response to be brand new */\n if ((request.response.statusCode || 0) < 400) {\n const error = findRootCause(request.error);\n request.response.statusCode = (error as ErrorWithStatus)?.statusCode || 500;\n request.response.body = error?.toString() ?? \"\";\n request.response.headers = request.response.headers ?? {};\n request.response.headers[\"content-type\"] = \"text/plain; charset=utf-8\";\n }\n\n logger.info(\"Response to API Gateway: \", request.response);\n },\n});\n\ntype ErrorWithStatus = Error & { statusCode?: number };\n\nfunction findRootCause(\n error: unknown | null | undefined,\n): ErrorWithStatus | Error | unknown | null | undefined {\n const errorWithStatus = error as ErrorWithStatus;\n if (errorWithStatus?.statusCode && errorWithStatus.statusCode >= 400) {\n return error as ErrorWithStatus;\n } else if (errorWithStatus?.cause) {\n return findRootCause(errorWithStatus.cause);\n } else {\n return error;\n }\n}\n","import { APIGatewayEventRequestContextWithAuthorizer } from \"aws-lambda\";\nimport middy from \"@middy/core\";\nimport { Logger } from \"@sailplane/logger\";\nimport { APIGatewayProxyEventAnyVersion } from \"./types\";\n\n/**\n * Middleware for LambdaUtils to set request context in Logger\n */\nexport const loggerContextMiddleware = (): middy.MiddlewareObj<APIGatewayProxyEventAnyVersion> => {\n return {\n before: async (request) => {\n Logger.setLambdaContext(request.context);\n\n const requestContext = request.event.requestContext;\n const claims =\n (requestContext as APIGatewayEventRequestContextWithAuthorizer<any>)?.authorizer?.claims || // API v1\n (requestContext as any)?.authorizer?.jwt?.claims; // API v2\n\n const context = {\n api_request_id: requestContext?.requestId,\n jwt_sub: claims?.sub,\n };\n Logger.addAttributes(context);\n },\n };\n};\n"],"mappings":"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","names":["Logger","Logger","logger","Logger"]}