From 3a17cff0b905ff9185ba1753e80b785950246c7e Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Fri, 17 Sep 2021 10:30:36 -0400 Subject: [PATCH 1/8] feat: sns and dynamodb event handlers --- __tests__/unit.api-gateway-v2.js | 4 +-- __tests__/utils.js | 56 +++++++++++++++++++++++++++++++ src/configure.d.ts | 3 ++ src/configure.js | 3 ++ src/event-sources/aws/dynamodb.js | 20 +++++++++++ src/event-sources/aws/sns.js | 20 +++++++++++ src/event-sources/index.js | 6 ++++ src/event-sources/utils.js | 16 +++++++-- src/event-sources/utils.test.ts | 56 ------------------------------- src/transport.js | 6 ++++ 10 files changed, 130 insertions(+), 60 deletions(-) create mode 100644 __tests__/utils.js create mode 100644 src/event-sources/aws/dynamodb.js create mode 100644 src/event-sources/aws/sns.js delete mode 100644 src/event-sources/utils.test.ts diff --git a/__tests__/unit.api-gateway-v2.js b/__tests__/unit.api-gateway-v2.js index 0cafa7ac..ecfb51ac 100644 --- a/__tests__/unit.api-gateway-v2.js +++ b/__tests__/unit.api-gateway-v2.js @@ -1,5 +1,5 @@ const eventSources = require('../src/event-sources') -const testUtils = require('../src/event-sources/utils.test') +const testUtils = require('./utils') const apiGatewayEventSource = eventSources.getEventSource({ eventSourceName: 'AWS_API_GATEWAY_V2' @@ -15,7 +15,7 @@ test('request has correct headers', () => { }) function getReq () { - const event = testUtils.sam_httpapi_event + const event = testUtils.samHttpApiEvent const request = apiGatewayEventSource.getRequest({ event }) return request } diff --git a/__tests__/utils.js b/__tests__/utils.js new file mode 100644 index 00000000..556b8846 --- /dev/null +++ b/__tests__/utils.js @@ -0,0 +1,56 @@ +const { getEventSourceNameBasedOnEvent } = require('../src/event-sources/utils') + +/** +This is an event delivered by `sam local start-api`, +using Type:HttpApi, with SAM CLI version 1.18.0. +*/ +const samHttpApiEvent = { + version: '2.0', + routeKey: 'GET /', + rawPath: '/', + rawQueryString: '', + cookies: [], + headers: { + Host: 'localhost:9000', + 'User-Agent': 'curl/7.64.1', + Accept: '*/*', + 'X-Forwarded-Proto': 'http', + 'X-Forwarded-Port': '9000' + }, + queryStringParameters: {}, + requestContext: { + accountId: '123456789012', + apiId: '1234567890', + http: { + method: 'GET', + path: '/', + protocol: 'HTTP/1.1', + sourceIp: '127.0.0.1', + userAgent: 'Custom User Agent String' + }, + requestId: 'aacf57f7-2fce-4069-a1f2-7cb4726dc028', + routeKey: 'GET /', + stage: null + }, + body: '', + pathParameters: {}, + stageVariables: null, + isBase64Encoded: false +} + +describe('getEventSourceNameBasedOnEvent', () => { + test('throws error on empty event', () => { + expect(() => getEventSourceNameBasedOnEvent({ event: {} })).toThrow( + 'Unable to determine event source based on event.' + ) + }) + + test('recognizes sam local HttpApi event', () => { + const result = getEventSourceNameBasedOnEvent({ event: samHttpApiEvent }) + expect(result).toEqual('AWS_API_GATEWAY_V2') + }) +}) + +module.exports = { + samHttpApiEvent +} diff --git a/src/configure.d.ts b/src/configure.d.ts index 714727c0..9080b66a 100644 --- a/src/configure.d.ts +++ b/src/configure.d.ts @@ -2,6 +2,8 @@ import { RequestListener } from "http"; import { Handler } from "aws-lambda"; import Logger from "./logger"; +type EventSources = "aws:sns" | "aws:dynamodb"; + interface EventSource { getRequest?: any; // TODO: getResponse?: any; // TODO: @@ -21,6 +23,7 @@ interface ConfigureParams { binaryMimeTypes?: string[]; binarySettings?: BinarySettings; eventSource?: EventSource; // TODO: + eventSourceRoutes?: { [key in EventSources]?: string }; } interface BinarySettings { diff --git a/src/configure.js b/src/configure.js index 9c66a75f..ec127883 100644 --- a/src/configure.js +++ b/src/configure.js @@ -25,6 +25,7 @@ function configure ({ resolutionMode: configureResolutionMode = 'PROMISE', eventSourceName: configureEventSourceName, eventSource: configureEventFns, + eventSourceRoutes: configureEventSourceRoutes, respondWithErrors: configureRespondWithErrors = process.env.NODE_ENV === 'development' } = {}) { function proxy ({ @@ -38,6 +39,7 @@ function configure ({ binaryMimeTypes = configureBinaryMimeTypes, binarySettings = configureBinarySettings || getDefaultBinarySettings(binaryMimeTypes), eventSource = configureEventFns || getEventSource({ eventSourceName }), + eventSourceRoutes = configureEventSourceRoutes || {}, log = configureLog, respondWithErrors = configureRespondWithErrors }) { @@ -77,6 +79,7 @@ function configure ({ eventSourceName, binarySettings, eventSource, + eventSourceRoutes, log }) } catch (error) { diff --git a/src/event-sources/aws/dynamodb.js b/src/event-sources/aws/dynamodb.js new file mode 100644 index 00000000..b6bfef67 --- /dev/null +++ b/src/event-sources/aws/dynamodb.js @@ -0,0 +1,20 @@ +const { emptyResponseMapper } = require('../utils') + +const getRequestValuesFromDynamoDB = ({ event }) => { + const method = 'POST' + const headers = {} + const body = event + const host = 'dynamodb.amazonaws.com' + + return { + method, + headers, + body, + host + } +} + +module.exports = { + getRequest: getRequestValuesFromDynamoDB, + getResponse: emptyResponseMapper +} diff --git a/src/event-sources/aws/sns.js b/src/event-sources/aws/sns.js new file mode 100644 index 00000000..61720f59 --- /dev/null +++ b/src/event-sources/aws/sns.js @@ -0,0 +1,20 @@ +const { emptyResponseMapper } = require('../utils') + +const getRequestValuesFromSns = ({ event }) => { + const method = 'POST' + const headers = {} + const body = event + const host = 'sns.amazonaws.com' + + return { + method, + headers, + body, + host + } +} + +module.exports = { + getRequest: getRequestValuesFromSns, + getResponse: emptyResponseMapper +} diff --git a/src/event-sources/index.js b/src/event-sources/index.js index 2f7ceac1..3b8b3566 100644 --- a/src/event-sources/index.js +++ b/src/event-sources/index.js @@ -2,6 +2,8 @@ const awsApiGatewayV1EventSource = require('./aws/api-gateway-v1') const awsApiGatewayV2EventSource = require('./aws/api-gateway-v2') const awsAlbEventSource = require('./aws/alb') const awsLambdaEdgeEventSource = require('./aws/lambda-edge') +const awsSnsEventSource = require('./aws/sns') +const awsDynamoDbEventSource = require('./aws/dynamodb') function getEventSource ({ eventSourceName }) { switch (eventSourceName) { @@ -13,6 +15,10 @@ function getEventSource ({ eventSourceName }) { return awsAlbEventSource case 'AWS_LAMBDA_EDGE': return awsLambdaEdgeEventSource + case 'AWS_DYNAMODB': + return awsDynamoDbEventSource + case 'AWS_SNS': + return awsSnsEventSource default: throw new Error('Couldn\'t detect valid event source.') } diff --git a/src/event-sources/utils.js b/src/event-sources/utils.js index 5db4ecbd..5f907956 100644 --- a/src/event-sources/utils.js +++ b/src/event-sources/utils.js @@ -71,7 +71,16 @@ function getEventSourceNameBasedOnEvent ({ event }) { if (event.requestContext && event.requestContext.elb) return 'AWS_ALB' - if (event.Records) return 'AWS_LAMBDA_EDGE' + if (event.Records) { + const eventSource = event.Records[0] ? event.Records[0].EventSource || event.Records[0].eventSource : undefined + if (eventSource === 'aws:sns') { + return 'AWS_SNS' + } + if (eventSource === 'aws:dynamodb') { + return 'AWS_DYNAMODB' + } + return 'AWS_LAMBDA_EDGE' + } if (event.requestContext) { return event.version === '2.0' ? 'AWS_API_GATEWAY_V2' : 'AWS_API_GATEWAY_V1' } @@ -95,11 +104,14 @@ function getCommaDelimitedHeaders ({ headersMap, separator = ',', lowerCaseKey = return commaDelimitedHeaders } +const emptyResponseMapper = () => () => {} + module.exports = { getPathWithQueryStringParams, getRequestValuesFromEvent, getMultiValueHeaders, getEventSourceNameBasedOnEvent, getEventBody, - getCommaDelimitedHeaders + getCommaDelimitedHeaders, + emptyResponseMapper } diff --git a/src/event-sources/utils.test.ts b/src/event-sources/utils.test.ts deleted file mode 100644 index b37f742d..00000000 --- a/src/event-sources/utils.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -const { getEventSourceNameBasedOnEvent } = require("./utils"); - -/** -This is an event delivered by `sam local start-api`, -using Type:HttpApi, with SAM CLI version 1.18.0. -*/ -const sam_httpapi_event = { - version: "2.0", - routeKey: "GET /", - rawPath: "/", - rawQueryString: "", - cookies: [], - headers: { - Host: "localhost:9000", - "User-Agent": "curl/7.64.1", - Accept: "*/*", - "X-Forwarded-Proto": "http", - "X-Forwarded-Port": "9000", - }, - queryStringParameters: {}, - requestContext: { - accountId: "123456789012", - apiId: "1234567890", - http: { - method: "GET", - path: "/", - protocol: "HTTP/1.1", - sourceIp: "127.0.0.1", - userAgent: "Custom User Agent String", - }, - requestId: "aacf57f7-2fce-4069-a1f2-7cb4726dc028", - routeKey: "GET /", - stage: null, - }, - body: "", - pathParameters: {}, - stageVariables: null, - isBase64Encoded: false, -}; - -describe("getEventSourceNameBasedOnEvent", () => { - test("throws error on empty event", () => { - expect(() => getEventSourceNameBasedOnEvent({ event: {} })).toThrow( - "Unable to determine event source based on event." - ); - }); - - test("recognizes sam local HttpApi event", () => { - const result = getEventSourceNameBasedOnEvent({ event: sam_httpapi_event }); - expect(result).toEqual("AWS_API_GATEWAY_V2"); - }); -}); - -module.exports = { - sam_httpapi_event, -}; diff --git a/src/transport.js b/src/transport.js index a647065d..88db1251 100644 --- a/src/transport.js +++ b/src/transport.js @@ -128,9 +128,15 @@ async function forwardRequestToNodeServer ({ eventSourceName, binarySettings, eventSource = getEventSource({ eventSourceName }), + eventSourceRoutes, log }) { const requestValues = eventSource.getRequest({ event, context, log }) + + if (!requestValues.path && eventSourceRoutes[eventSourceName]) { + requestValues.path = eventSourceRoutes[eventSourceName] + } + log.debug('SERVERLESS_EXPRESS:FORWARD_REQUEST_TO_NODE_SERVER:REQUEST_VALUES', { requestValues }) const { request, response } = await getRequestResponse(requestValues) await framework.sendRequest({ app, request, response }) From 881e45309a37d4d2a0388d2c0e28566c4a9f646e Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Fri, 17 Sep 2021 10:56:11 -0400 Subject: [PATCH 2/8] feat: unit tests for sns and dynamodb --- __tests__/unit.dynamodb.js | 20 ++++++ __tests__/unit.sns.js | 20 ++++++ __tests__/utils.js | 116 +++++++++++++++++++++++++++++- src/event-sources/aws/dynamodb.js | 6 +- src/event-sources/aws/sns.js | 6 +- 5 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 __tests__/unit.dynamodb.js create mode 100644 __tests__/unit.sns.js diff --git a/__tests__/unit.dynamodb.js b/__tests__/unit.dynamodb.js new file mode 100644 index 00000000..eba176bc --- /dev/null +++ b/__tests__/unit.dynamodb.js @@ -0,0 +1,20 @@ +const eventSources = require('../src/event-sources') +const testUtils = require('./utils') + +const dynamodbEventSource = eventSources.getEventSource({ + eventSourceName: 'AWS_DYNAMODB' +}) + +test('request is correct', () => { + const req = getReq() + expect(typeof req).toEqual('object') + expect(req.headers).toEqual({ Host: 'dynamodb.amazonaws.com' }) + expect(req.body).toEqual(testUtils.dynamoDbEvent) + expect(req.method).toEqual('POST') +}) + +function getReq () { + const event = testUtils.dynamoDbEvent + const request = dynamodbEventSource.getRequest({ event }) + return request +} diff --git a/__tests__/unit.sns.js b/__tests__/unit.sns.js new file mode 100644 index 00000000..e4088fe7 --- /dev/null +++ b/__tests__/unit.sns.js @@ -0,0 +1,20 @@ +const eventSources = require('../src/event-sources') +const testUtils = require('./utils') + +const snsEventSource = eventSources.getEventSource({ + eventSourceName: 'AWS_SNS' +}) + +test('request is correct', () => { + const req = getReq() + expect(typeof req).toEqual('object') + expect(req.headers).toEqual({ Host: 'sns.amazonaws.com' }) + expect(req.body).toEqual(testUtils.snsEvent) + expect(req.method).toEqual('POST') +}) + +function getReq () { + const event = testUtils.snsEvent + const request = snsEventSource.getRequest({ event }) + return request +} diff --git a/__tests__/utils.js b/__tests__/utils.js index 556b8846..9b9e4cb5 100644 --- a/__tests__/utils.js +++ b/__tests__/utils.js @@ -38,6 +38,108 @@ const samHttpApiEvent = { isBase64Encoded: false } +// Sample event from https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html +const dynamoDbEvent = { + Records: [ + { + eventID: '1', + eventVersion: '1.0', + dynamodb: { + Keys: { + Id: { + N: '101' + } + }, + NewImage: { + Message: { + S: 'New item!' + }, + Id: { + N: '101' + } + }, + StreamViewType: 'NEW_AND_OLD_IMAGES', + SequenceNumber: '111', + SizeBytes: 26 + }, + awsRegion: 'us-west-2', + eventName: 'INSERT', + eventSourceARN: 'arn:aws:dynamodb:us-east-1:0000000000:mytable', + eventSource: 'aws:dynamodb' + }, + { + eventID: '2', + eventVersion: '1.0', + dynamodb: { + OldImage: { + Message: { + S: 'New item!' + }, + Id: { + N: '101' + } + }, + SequenceNumber: '222', + Keys: { + Id: { + N: '101' + } + }, + SizeBytes: 59, + NewImage: { + Message: { + S: 'This item has changed' + }, + Id: { + N: '101' + } + }, + StreamViewType: 'NEW_AND_OLD_IMAGES' + }, + awsRegion: 'us-west-2', + eventName: 'MODIFY', + eventSourceARN: 'arn:aws:dynamodb:us-east-1:0000000000:mytable', + eventSource: 'aws:dynamodb' + } + ] +} + +// Sample event from https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html +const snsEvent = { + Records: [ + { + EventVersion: '1.0', + EventSubscriptionArn: + 'arn:aws:sns:us-east-2:123456789012:sns-lambda:21be56ed-a058-49f5-8c98-aedd2564c486', + EventSource: 'aws:sns', + Sns: { + SignatureVersion: '1', + Timestamp: '2019-01-02T12:45:07.000Z', + Signature: 'tcc6faL2yUC6dgZdmrwh1Y4cGa/ebXEkAi6RibDsvpi+tE/1+82j...65r==', + SigningCertUrl: + 'https://sns.us-east-2.amazonaws.com/SimpleNotificationService-ac565b8b1a6c5d002d285f9598aa1d9b.pem', + MessageId: '95df01b4-ee98-5cb9-9903-4c221d41eb5e', + Message: 'Hello from SNS!', + MessageAttributes: { + Test: { + Type: 'String', + Value: 'TestString' + }, + TestBinary: { + Type: 'Binary', + Value: 'TestBinary' + } + }, + Type: 'Notification', + UnsubscribeUrl: + 'https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:123456789012:test-lambda:21be56ed-a058-49f5-8c98-aedd2564c486', + TopicArn: 'arn:aws:sns:us-east-2:123456789012:sns-lambda', + Subject: 'TestInvoke' + } + } + ] +} + describe('getEventSourceNameBasedOnEvent', () => { test('throws error on empty event', () => { expect(() => getEventSourceNameBasedOnEvent({ event: {} })).toThrow( @@ -49,8 +151,20 @@ describe('getEventSourceNameBasedOnEvent', () => { const result = getEventSourceNameBasedOnEvent({ event: samHttpApiEvent }) expect(result).toEqual('AWS_API_GATEWAY_V2') }) + + test('recognizes dynamodb event', () => { + const result = getEventSourceNameBasedOnEvent({ event: dynamoDbEvent }) + expect(result).toEqual('AWS_DYNAMODB') + }) + + test('recognizes sns event', () => { + const result = getEventSourceNameBasedOnEvent({ event: snsEvent }) + expect(result).toEqual('AWS_SNS') + }) }) module.exports = { - samHttpApiEvent + samHttpApiEvent, + dynamoDbEvent, + snsEvent } diff --git a/src/event-sources/aws/dynamodb.js b/src/event-sources/aws/dynamodb.js index b6bfef67..5c5b1bce 100644 --- a/src/event-sources/aws/dynamodb.js +++ b/src/event-sources/aws/dynamodb.js @@ -2,15 +2,13 @@ const { emptyResponseMapper } = require('../utils') const getRequestValuesFromDynamoDB = ({ event }) => { const method = 'POST' - const headers = {} + const headers = { Host: 'dynamodb.amazonaws.com' } const body = event - const host = 'dynamodb.amazonaws.com' return { method, headers, - body, - host + body } } diff --git a/src/event-sources/aws/sns.js b/src/event-sources/aws/sns.js index 61720f59..71f0e69d 100644 --- a/src/event-sources/aws/sns.js +++ b/src/event-sources/aws/sns.js @@ -2,15 +2,13 @@ const { emptyResponseMapper } = require('../utils') const getRequestValuesFromSns = ({ event }) => { const method = 'POST' - const headers = {} + const headers = { Host: 'sns.amazonaws.com' } const body = event - const host = 'sns.amazonaws.com' return { method, headers, - body, - host + body } } From 2b96348a3bbc5043782c32aa24aed53be52e5fa4 Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Mon, 4 Oct 2021 10:15:15 -0700 Subject: [PATCH 3/8] feat: update tests --- __tests__/unit.js | 47 +++++++++++++++++++++----------------- jest-helpers/index.js | 7 +++++- src/configure.d.ts | 2 +- src/configure.js | 1 + src/event-sources/utils.js | 2 +- src/make-resolver.js | 5 ++++ src/transport.js | 11 +++++++++ 7 files changed, 51 insertions(+), 24 deletions(-) diff --git a/__tests__/unit.js b/__tests__/unit.js index cb8675e7..b10dc7d6 100644 --- a/__tests__/unit.js +++ b/__tests__/unit.js @@ -114,15 +114,17 @@ test('getRequestResponse: without headers', async (done) => { describe('respondToEventSourceWithError', () => { test('responds with 500 status', () => { return new Promise( - (resolve) => { - const context = new MockContext(resolve) + (resolve, reject) => { + const context = new MockContext(resolve, reject) const contextResolver = { - succeed: (p) => context.succeed(p.response) + succeed: (p) => context.succeed(p.response), + fail: (p) => context.fail(p.error) } serverlessExpressTransport.respondToEventSourceWithError({ error: new Error('ERROR'), resolver: contextResolver, log, + eventSourceName: 'AWS_API_GATEWAY_V1', eventSource: apiGatewayEventSource }) } @@ -135,16 +137,18 @@ describe('respondToEventSourceWithError', () => { }) test('responds with 500 status and stack trace', () => { return new Promise( - (resolve) => { - const context = new MockContext(resolve) + (resolve, reject) => { + const context = new MockContext(resolve, reject) const contextResolver = { - succeed: (p) => context.succeed(p.response) + succeed: (p) => context.succeed(p.response), + fail: (p) => context.fail(p.error) } serverlessExpressTransport.respondToEventSourceWithError({ error: new Error('There was an error...'), resolver: contextResolver, log, respondWithErrors: true, + eventSourceName: 'AWS_API_GATEWAY_V1', eventSource: apiGatewayEventSource }) } @@ -160,10 +164,11 @@ describe('respondToEventSourceWithError', () => { }) }) -function getContextResolver (resolve) { - const context = new MockContext(resolve) +function getContextResolver (resolve, reject) { + const context = new MockContext(resolve, reject) const contextResolver = { - succeed: (p) => context.succeed(p.response) + succeed: (p) => context.succeed(p.response), + fail: (p) => context.fail(p.error) } return contextResolver @@ -176,8 +181,8 @@ describe.skip('forwardResponse: content-type encoding', () => { const { requestResponse } = await getReqRes(multiValueHeaders) const response = new ServerlessResponse(requestResponse.request) return new Promise( - (resolve) => { - const contextResolver = getContextResolver(resolve) + (resolve, reject) => { + const contextResolver = getContextResolver(resolve, reject) serverlessExpressTransport.forwardResponse({ binaryMimeTypes, response, @@ -201,8 +206,8 @@ describe.skip('forwardResponse: content-type encoding', () => { const body = 'hello world' const response = new ServerlessResponse(200, multiValueHeaders, body) return new Promise( - (resolve) => { - const contextResolver = getContextResolver(resolve) + (resolve, reject) => { + const contextResolver = getContextResolver(resolve, reject) serverlessExpressTransport.forwardResponse({ binaryMimeTypes, response, @@ -225,8 +230,8 @@ describe.skip('forwardResponse: content-type encoding', () => { const body = JSON.stringify({ hello: 'world' }) const response = new ServerlessResponse(200, multiValueHeaders, body) return new Promise( - (resolve) => { - const contextResolver = getContextResolver(resolve) + (resolve, reject) => { + const contextResolver = getContextResolver(resolve, reject) serverlessExpressTransport.forwardResponse({ binaryMimeTypes, response, @@ -249,8 +254,8 @@ describe.skip('forwardResponse: content-type encoding', () => { const body = 'hello world' const response = new ServerlessResponse(200, multiValueHeaders, body) return new Promise( - (resolve) => { - const contextResolver = getContextResolver(resolve) + (resolve, reject) => { + const contextResolver = getContextResolver(resolve, reject) serverlessExpressTransport.forwardResponse({ binaryMimeTypes, response, @@ -273,8 +278,8 @@ describe.skip('forwardResponse: content-type encoding', () => { const body = 'hello world' const response = new ServerlessResponse(200, multiValueHeaders, body) return new Promise( - (resolve) => { - const contextResolver = getContextResolver(resolve) + (resolve, reject) => { + const contextResolver = getContextResolver(resolve, reject) serverlessExpressTransport.forwardResponse({ binaryMimeTypes, response, @@ -295,8 +300,8 @@ describe.skip('forwardResponse: content-type encoding', () => { describe('makeResolver', () => { test('CONTEXT (specified)', () => { return new Promise( - (resolve) => { - const context = new MockContext(resolve) + (resolve, reject) => { + const context = new MockContext(resolve, reject) const contextResolver = makeResolver({ context, resolutionMode: 'CONTEXT' diff --git a/jest-helpers/index.js b/jest-helpers/index.js index 9742f467..75123099 100644 --- a/jest-helpers/index.js +++ b/jest-helpers/index.js @@ -29,13 +29,18 @@ const log = { } class MockContext { - constructor (resolve) { + constructor (resolve, reject) { this.resolve = resolve + this.reject = reject } succeed (successResponse) { this.resolve(successResponse) } + + fail (error) { + this.reject(error) + } } function makeEvent ({ eventSourceName, ...rest }) { diff --git a/src/configure.d.ts b/src/configure.d.ts index 9080b66a..4ad0088a 100644 --- a/src/configure.d.ts +++ b/src/configure.d.ts @@ -2,7 +2,7 @@ import { RequestListener } from "http"; import { Handler } from "aws-lambda"; import Logger from "./logger"; -type EventSources = "aws:sns" | "aws:dynamodb"; +type EventSources = "AWS_SNS" | "AWS_DYNAMODB"; interface EventSource { getRequest?: any; // TODO: diff --git a/src/configure.js b/src/configure.js index ec127883..929b40d9 100644 --- a/src/configure.js +++ b/src/configure.js @@ -88,6 +88,7 @@ function configure ({ resolver, log, respondWithErrors, + eventSourceName, eventSource }) } diff --git a/src/event-sources/utils.js b/src/event-sources/utils.js index 5f907956..a2fb94fe 100644 --- a/src/event-sources/utils.js +++ b/src/event-sources/utils.js @@ -104,7 +104,7 @@ function getCommaDelimitedHeaders ({ headersMap, separator = ',', lowerCaseKey = return commaDelimitedHeaders } -const emptyResponseMapper = () => () => {} +const emptyResponseMapper = () => {} module.exports = { getPathWithQueryStringParams, diff --git a/src/make-resolver.js b/src/make-resolver.js index 8f7a7173..9e771e3a 100644 --- a/src/make-resolver.js +++ b/src/make-resolver.js @@ -9,6 +9,11 @@ function makeResolver ({ if (resolutionMode === 'CONTEXT') return context.succeed(response) if (resolutionMode === 'CALLBACK') return callback(null, response) if (resolutionMode === 'PROMISE') return promise.resolve(response) + }, + fail: ({ error }) => { + if (resolutionMode === 'CONTEXT') return context.fail(error) + if (resolutionMode === 'CALLBACK') return callback(error, null) + if (resolutionMode === 'PROMISE') return promise.reject(error) } } } diff --git a/src/transport.js b/src/transport.js index 88db1251..073530eb 100644 --- a/src/transport.js +++ b/src/transport.js @@ -52,10 +52,21 @@ function respondToEventSourceWithError ({ resolver, log, respondWithErrors, + eventSourceName, eventSource }) { log.error('SERVERLESS_EXPRESS:RESPOND_TO_EVENT_SOURCE_WITH_ERROR', error) + if ( + eventSourceName !== 'AWS_ALB' && + eventSourceName !== 'AWS_LAMBDA_EDGE' && + eventSourceName !== 'AWS_API_GATEWAY_V1' && + eventSourceName !== 'AWS_API_GATEWAY_V2' + ) { + resolver.fail({ error }) + return + } + const body = respondWithErrors ? error.stack : '' const errorResponse = eventSource.getResponse({ statusCode: 500, From 0d34a9cecd7b259c1cc634d0e55fd0fbeb356c06 Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Mon, 4 Oct 2021 10:38:10 -0700 Subject: [PATCH 4/8] feat: update readme --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38a76136..decfe4cb 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,50 @@ serverlessExpress({ }) ``` +#### eventSourceRoutes + +Introduced in `@vendia/serverless-express@4.4.0` native support for `aws:sns` and `aws:dynamodb` events were introduced. + +A single function can be configured to handle events from SNS and DynamoDB, as well as the previously supported events. + +Assuming the following function configuration in `serverless.yml`: + +```yaml +functions: + lambda-handler: + handler: src/lambda.handler + events: + - http: + path: / + method: get + - sns: + topicName: my-topic + - stream: + type: dynamodb + arn: arn:aws:dynamodb:us-east-1:012345678990:table/my-table/stream/2021-07-15T15:05:51.683 +``` + +And the following configuration: + +```js +serverlessExpress({ + app, + eventSourceRoutes: { + 'AWS_SNS': '/sns', + 'AWS_DYNAMODB': '/dynamodb' + } +}) +``` + +Events from SNS and DynamoDB will `POST` to the routes configured in Express to handle `/sns` and `/dynamodb`, +respectively. + +Also, to ensure the events propagated from an internal event and not externally, it is **highly recommended** to +ensure the `Host` header matches: + + - SNS: `sns.amazonaws.com` + - DynamoDB: `dynamodb.amazonaws.com` + ### logSettings Specify log settings that are passed to the default logger. Currently, you can only set the log `level`. diff --git a/package.json b/package.json index 629eae6c..0db7e032 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vendia/serverless-express", - "version": "4.3.11", + "version": "4.4.0", "description": "This library enables you to utilize AWS Lambda and Amazon API Gateway to respond to web and API requests using your existing Node.js application framework.", "keywords": [ "aws", From ecc371a258280b0bab345c23318df708bb7cf41b Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Mon, 4 Oct 2021 12:55:58 -0700 Subject: [PATCH 5/8] fix: lowercase headers --- __tests__/unit.dynamodb.js | 2 +- __tests__/unit.sns.js | 2 +- src/event-sources/aws/dynamodb.js | 2 +- src/event-sources/aws/sns.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/__tests__/unit.dynamodb.js b/__tests__/unit.dynamodb.js index eba176bc..f9288f79 100644 --- a/__tests__/unit.dynamodb.js +++ b/__tests__/unit.dynamodb.js @@ -8,7 +8,7 @@ const dynamodbEventSource = eventSources.getEventSource({ test('request is correct', () => { const req = getReq() expect(typeof req).toEqual('object') - expect(req.headers).toEqual({ Host: 'dynamodb.amazonaws.com' }) + expect(req.headers).toEqual({ host: 'dynamodb.amazonaws.com' }) expect(req.body).toEqual(testUtils.dynamoDbEvent) expect(req.method).toEqual('POST') }) diff --git a/__tests__/unit.sns.js b/__tests__/unit.sns.js index e4088fe7..1f963ad7 100644 --- a/__tests__/unit.sns.js +++ b/__tests__/unit.sns.js @@ -8,7 +8,7 @@ const snsEventSource = eventSources.getEventSource({ test('request is correct', () => { const req = getReq() expect(typeof req).toEqual('object') - expect(req.headers).toEqual({ Host: 'sns.amazonaws.com' }) + expect(req.headers).toEqual({ host: 'sns.amazonaws.com' }) expect(req.body).toEqual(testUtils.snsEvent) expect(req.method).toEqual('POST') }) diff --git a/src/event-sources/aws/dynamodb.js b/src/event-sources/aws/dynamodb.js index 5c5b1bce..014f44a1 100644 --- a/src/event-sources/aws/dynamodb.js +++ b/src/event-sources/aws/dynamodb.js @@ -2,7 +2,7 @@ const { emptyResponseMapper } = require('../utils') const getRequestValuesFromDynamoDB = ({ event }) => { const method = 'POST' - const headers = { Host: 'dynamodb.amazonaws.com' } + const headers = { host: 'dynamodb.amazonaws.com' } const body = event return { diff --git a/src/event-sources/aws/sns.js b/src/event-sources/aws/sns.js index 71f0e69d..8d8de2d0 100644 --- a/src/event-sources/aws/sns.js +++ b/src/event-sources/aws/sns.js @@ -2,7 +2,7 @@ const { emptyResponseMapper } = require('../utils') const getRequestValuesFromSns = ({ event }) => { const method = 'POST' - const headers = { Host: 'sns.amazonaws.com' } + const headers = { host: 'sns.amazonaws.com' } const body = event return { From 27249b47b35621707cd95764737476bc2ca90ebd Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Wed, 6 Oct 2021 09:06:44 -0700 Subject: [PATCH 6/8] feat: remove version modificaiton --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0db7e032..629eae6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vendia/serverless-express", - "version": "4.4.0", + "version": "4.3.11", "description": "This library enables you to utilize AWS Lambda and Amazon API Gateway to respond to web and API requests using your existing Node.js application framework.", "keywords": [ "aws", From 01d092f2bb8f8b837048d0c6a3a84838c17053ab Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Wed, 6 Oct 2021 09:33:51 -0700 Subject: [PATCH 7/8] fix: dynamodb stream example --- examples/dynamodb-stream/.gitignore | 2 + examples/dynamodb-stream/README.md | 8 + examples/dynamodb-stream/dynamodb-event.json | 28 + examples/dynamodb-stream/package-lock.json | 1564 ++++++++++++++++++ examples/dynamodb-stream/package.json | 22 + examples/dynamodb-stream/scripts/local.js | 28 + examples/dynamodb-stream/src/app.js | 31 + examples/dynamodb-stream/src/lambda.js | 10 + examples/dynamodb-stream/template.yaml | 38 + 9 files changed, 1731 insertions(+) create mode 100644 examples/dynamodb-stream/.gitignore create mode 100644 examples/dynamodb-stream/README.md create mode 100644 examples/dynamodb-stream/dynamodb-event.json create mode 100644 examples/dynamodb-stream/package-lock.json create mode 100644 examples/dynamodb-stream/package.json create mode 100644 examples/dynamodb-stream/scripts/local.js create mode 100644 examples/dynamodb-stream/src/app.js create mode 100644 examples/dynamodb-stream/src/lambda.js create mode 100644 examples/dynamodb-stream/template.yaml diff --git a/examples/dynamodb-stream/.gitignore b/examples/dynamodb-stream/.gitignore new file mode 100644 index 00000000..6652e7c7 --- /dev/null +++ b/examples/dynamodb-stream/.gitignore @@ -0,0 +1,2 @@ +template.packaged.yaml +*.tgz \ No newline at end of file diff --git a/examples/dynamodb-stream/README.md b/examples/dynamodb-stream/README.md new file mode 100644 index 00000000..4e017627 --- /dev/null +++ b/examples/dynamodb-stream/README.md @@ -0,0 +1,8 @@ +## Development + +``` +npm pack ../.. +npm install ./vendia-serverless-express-4.0.0-rc.1.tgz +npm install --prefix ./src ./ +node local.js +``` \ No newline at end of file diff --git a/examples/dynamodb-stream/dynamodb-event.json b/examples/dynamodb-stream/dynamodb-event.json new file mode 100644 index 00000000..5c7c5780 --- /dev/null +++ b/examples/dynamodb-stream/dynamodb-event.json @@ -0,0 +1,28 @@ +{ + "Records": [ + { + "eventID": "93eea905b34daefbe0fe8a21858065a8", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "dynamodb": { + "ApproximateCreationDateTime": 1560902007, + "Keys": { + "id": { + "S": "b" + } + }, + "NewImage": { + "id": { + "S": "b" + } + }, + "SequenceNumber": "200000000000235577791", + "SizeBytes": 6, + "StreamViewType": "NEW_IMAGE" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123123123:table/serverless-express-dynamodb-example-Table-191RH2M1OU01V/stream/2019-06-18T23:42:52.038" + } + ] +} \ No newline at end of file diff --git a/examples/dynamodb-stream/package-lock.json b/examples/dynamodb-stream/package-lock.json new file mode 100644 index 00000000..a5821abc --- /dev/null +++ b/examples/dynamodb-stream/package-lock.json @@ -0,0 +1,1564 @@ +{ + "name": "@vendia/serverless-alb-example", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@vendia/serverless-alb-example", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@vendia/serverless-express": "^4.3.0", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "express": "^4.17.1", + "pug": "^3.0.1" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + }, + "node_modules/@babel/parser": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.9.tgz", + "integrity": "sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz", + "integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@vendia/serverless-express": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@vendia/serverless-express/-/serverless-express-4.3.0.tgz", + "integrity": "sha512-Z5CGuFJYg6lUTH9RPq5CqmnhCs8/UIOmpe/6wcWYmrsfWpnbs+3xG6TE7NJ0ln3H9TTpinv6ogh47PBP7b2+Dw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "node_modules/assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "node_modules/is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dependencies": { + "mime-db": "1.40.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pug": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.1.tgz", + "integrity": "sha512-9v1o2yXMfSKJy2PykKyWUhpgx9Pf9D/UlPgIs2pTTxR6DQZ0oivy4I9f8PlWXRY4sjIhDU4TMJ7hQmYnNJc2bw==", + "dependencies": { + "pug-code-gen": "^3.0.2", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.0", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.0", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", + "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + }, + "node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } + } + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + }, + "@babel/parser": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.9.tgz", + "integrity": "sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw==" + }, + "@babel/types": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz", + "integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==", + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@vendia/serverless-express": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@vendia/serverless-express/-/serverless-express-4.3.0.tgz", + "integrity": "sha512-Z5CGuFJYg6lUTH9RPq5CqmnhCs8/UIOmpe/6wcWYmrsfWpnbs+3xG6TE7NJ0ln3H9TTpinv6ogh47PBP7b2+Dw==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" + }, + "babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "requires": { + "@babel/types": "^7.9.6" + } + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, + "constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "requires": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "requires": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "pug": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.1.tgz", + "integrity": "sha512-9v1o2yXMfSKJy2PykKyWUhpgx9Pf9D/UlPgIs2pTTxR6DQZ0oivy4I9f8PlWXRY4sjIhDU4TMJ7hQmYnNJc2bw==", + "requires": { + "pug-code-gen": "^3.0.2", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.0", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.0", + "pug-strip-comments": "^2.0.0" + } + }, + "pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "requires": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "pug-code-gen": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", + "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", + "requires": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" + }, + "pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "requires": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "requires": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "requires": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "requires": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "requires": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==" + }, + "pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "requires": { + "pug-error": "^2.0.0" + } + }, + "pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" + }, + "with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "requires": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } + } + } +} diff --git a/examples/dynamodb-stream/package.json b/examples/dynamodb-stream/package.json new file mode 100644 index 00000000..e33b6484 --- /dev/null +++ b/examples/dynamodb-stream/package.json @@ -0,0 +1,22 @@ +{ + "name": "@vendia/dynamodb-stream", + "version": "1.0.0", + "description": "Example application for capturing DynamoDB stream events", + "main": "src/index.js", + "scripts": { + "#postinstall": "mv node_modules src/node_modules", + "package": "aws cloudformation package --template-file ./template.yaml --output-template-file template.packaged.yaml --s3-bucket artifacts-123nsef", + "deploy": "aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name ServerlessExpressDynamoDbExample --capabilities CAPABILITY_IAM", + "package-deploy": "npm run package && npm run deploy", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@vendia/serverless-express": "^4.3.0", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "express": "^4.17.1", + "pug": "^3.0.1" + } +} diff --git a/examples/dynamodb-stream/scripts/local.js b/examples/dynamodb-stream/scripts/local.js new file mode 100644 index 00000000..d94f5d72 --- /dev/null +++ b/examples/dynamodb-stream/scripts/local.js @@ -0,0 +1,28 @@ +const lambdaFunction = require('../src/lambda.js') +const albEvent = require('../dynamodb-event.json') + +const server = lambdaFunction.handler(albEvent, {}) + .then(v => { + console.info(v) + process.exit(0) + }) + .catch(v => { + console.error(v) + process.exit(1) + }) + +process.stdin.resume() + +function exitHandler (options, err) { + if (options.cleanup && server && server.close) { + server.close() + } + + if (err) console.error(err.stack) + if (options.exit) process.exit() +} + +process.on('exit', exitHandler.bind(null, { cleanup: true })) +process.on('SIGINT', exitHandler.bind(null, { exit: true })) // ctrl+c event +process.on('SIGTSTP', exitHandler.bind(null, { exit: true })) // ctrl+v event +process.on('uncaughtException', exitHandler.bind(null, { exit: true })) diff --git a/examples/dynamodb-stream/src/app.js b/examples/dynamodb-stream/src/app.js new file mode 100644 index 00000000..a45090dc --- /dev/null +++ b/examples/dynamodb-stream/src/app.js @@ -0,0 +1,31 @@ +const express = require('express') +const bodyParser = require('body-parser') +const app = express() +const router = express.Router() + +router.use(bodyParser.json()) +router.use(bodyParser.urlencoded({ extended: true })) + +router.get('/events/history', (req, res) => { + res.json(events) +}) + +router.post('/events/dynamodb', (req, res) => { + if (req.header('host') !== 'dynamodb.amazonaws.com') { + res.status(403).json({ message: 'Forbidden' }) + return + } + events.push(req.body) + res.status(201) +}) + +// Ephemeral in-memory data store +const events = [] + +// The serverless-express library creates a server and listens on a Unix +// Domain Socket for you, so you can remove the usual call to app.listen. +// app.listen(3000) +app.use('/', router) + +// Export your express server so you can import it in the lambda function. +module.exports = app diff --git a/examples/dynamodb-stream/src/lambda.js b/examples/dynamodb-stream/src/lambda.js new file mode 100644 index 00000000..dba7f385 --- /dev/null +++ b/examples/dynamodb-stream/src/lambda.js @@ -0,0 +1,10 @@ +require('source-map-support/register') +const serverlessExpress = require('@vendia/serverless-express') +const app = require('./app') + +exports.handler = serverlessExpress({ + app, + eventSourceRoutes: { + AWS_DYNAMODB: '/events/dynamodb' + } +}) diff --git a/examples/dynamodb-stream/template.yaml b/examples/dynamodb-stream/template.yaml new file mode 100644 index 00000000..5e208ff3 --- /dev/null +++ b/examples/dynamodb-stream/template.yaml @@ -0,0 +1,38 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: serverless-express + DynamoDB Stream example +Resources: + ServerlessExpressFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./src + Handler: lambda.handler + MemorySize: 1024 + Runtime: nodejs12.x + Timeout: 3 + Events: + DynamoDB1: + Type: DynamoDB + Properties: + Stream: !GetAtt Table.StreamArn + StartingPosition: TRIM_HORIZON + BatchSize: 1 + Table: + Type: 'AWS::DynamoDB::Table' + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + ProvisionedThroughput: + ReadCapacityUnits: 5 + WriteCapacityUnits: 5 + StreamSpecification: + StreamViewType: NEW_IMAGE + +Outputs: + LambdaFunctionConsoleUrl: + Description: Console URL for the Lambda Function. + Value: !Sub https://${AWS::Region}.console.aws.amazon.com/lambda/home?region=${AWS::Region}#/functions/${ServerlessExpressFunction} From 1e6005f9977ce4e4cddfff3de5f6520a81015531 Mon Sep 17 00:00:00 2001 From: Christian Nuss Date: Wed, 6 Oct 2021 09:35:52 -0700 Subject: [PATCH 8/8] fix: @vendia/serverless-express 4.4.0 --- examples/dynamodb-stream/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dynamodb-stream/package.json b/examples/dynamodb-stream/package.json index e33b6484..e34ca461 100644 --- a/examples/dynamodb-stream/package.json +++ b/examples/dynamodb-stream/package.json @@ -13,7 +13,7 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@vendia/serverless-express": "^4.3.0", + "@vendia/serverless-express": "^4.4.0", "body-parser": "^1.19.0", "cors": "^2.8.5", "express": "^4.17.1",