UNPKG

@h4ad/serverless-adapter

Version:

Run REST APIs and other web applications using your existing Node.js application framework (NestJS, Express, Koa, Hapi, Fastify and many others), on top of AWS, Azure, Digital Ocean and many other clouds.

1 lines 80.1 kB
{"version":3,"sources":["../../../src/adapters/aws/alb.adapter.ts","../../../src/adapters/aws/api-gateway-v1.adapter.ts","../../../src/adapters/aws/api-gateway-v2.adapter.ts","../../../src/adapters/aws/base/aws-simple-adapter.ts","../../../src/adapters/aws/dynamodb.adapter.ts","../../../src/adapters/aws/event-bridge.adapter.ts","../../../src/adapters/aws/lambda-edge.adapter.ts","../../../src/adapters/aws/s3.adapter.ts","../../../src/adapters/aws/sns.adapter.ts","../../../src/adapters/aws/sqs.adapter.ts","../../../src/adapters/aws/request-lambda-edge.adapter.ts"],"sourcesContent":["//#region Imports\n\nimport type { ALBEvent, ALBResult, Context } from 'aws-lambda';\nimport type {\n AdapterContract,\n AdapterRequest,\n GetResponseAdapterProps,\n OnErrorProps,\n} from '../../contracts';\nimport {\n type StripBasePathFn,\n buildStripBasePath,\n getEventBodyAsBuffer,\n getFlattenedHeadersMap,\n getMultiValueHeadersMap,\n getPathWithQueryStringParams,\n} from '../../core';\n\n//#endregion\n\n/**\n * The options to customize the {@link AlbAdapter}\n *\n * @breadcrumb Adapters / AWS / AlbAdapter\n * @public\n */\nexport interface AlbAdapterOptions {\n /**\n * Strip base path for custom domains\n *\n * @defaultValue ''\n */\n stripBasePath?: string;\n}\n\n/**\n * The adapter to handle requests from AWS ALB\n *\n * @example\n * ```typescript\n * const stripBasePath = '/any/custom/base/path'; // default ''\n * const adapter = new AlbAdapter({ stripBasePath });\n * ```\n *\n * {@link https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html | Event Reference}\n *\n * @breadcrumb Adapters / AWS / AlbAdapter\n * @public\n */\nexport class AlbAdapter\n implements AdapterContract<ALBEvent, Context, ALBResult>\n{\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link AlbAdapter}\n */\n constructor(protected readonly options?: AlbAdapterOptions) {\n this.stripPathFn = buildStripBasePath(this.options?.stripBasePath);\n }\n\n //#endregion\n\n //#region Protected Properties\n\n /**\n * Strip base path function\n */\n protected stripPathFn: StripBasePathFn;\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public getAdapterName(): string {\n return AlbAdapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public canHandle(event: unknown): event is ALBEvent {\n const albEvent = event as Partial<ALBEvent>;\n\n return !!(albEvent?.requestContext && albEvent.requestContext.elb);\n }\n\n /**\n * {@inheritDoc}\n */\n public getRequest(event: ALBEvent): AdapterRequest {\n const method = event.httpMethod;\n const path = this.getPathFromEvent(event);\n\n const headers = event.multiValueHeaders\n ? getFlattenedHeadersMap(event.multiValueHeaders, ',', true)\n : event.headers!;\n\n let body: Buffer | undefined;\n\n if (event.body) {\n const [bufferBody, contentLength] = getEventBodyAsBuffer(\n event.body,\n event.isBase64Encoded,\n );\n\n body = bufferBody;\n headers['content-length'] = String(contentLength);\n }\n\n let remoteAddress = '';\n\n // ref: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/x-forwarded-headers.html#x-forwarded-for\n if (headers['x-forwarded-for']) remoteAddress = headers['x-forwarded-for'];\n\n return {\n method,\n headers,\n body,\n remoteAddress,\n path,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public getResponse({\n event,\n headers: responseHeaders,\n body,\n isBase64Encoded,\n statusCode,\n }: GetResponseAdapterProps<ALBEvent>): ALBResult {\n const multiValueHeaders = !event.headers\n ? getMultiValueHeadersMap(responseHeaders)\n : undefined;\n\n const headers = event.headers\n ? getFlattenedHeadersMap(responseHeaders)\n : undefined;\n\n if (headers && headers['transfer-encoding'] === 'chunked')\n delete headers['transfer-encoding'];\n\n if (\n multiValueHeaders &&\n multiValueHeaders['transfer-encoding']?.includes('chunked')\n )\n delete multiValueHeaders['transfer-encoding'];\n\n return {\n statusCode,\n body,\n headers,\n multiValueHeaders,\n isBase64Encoded,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public onErrorWhileForwarding({\n error,\n delegatedResolver,\n respondWithErrors,\n event,\n log,\n }: OnErrorProps<ALBEvent, ALBResult>): void {\n const body = respondWithErrors ? error.stack || '' : '';\n const errorResponse = this.getResponse({\n event,\n statusCode: 500,\n body,\n headers: {},\n isBase64Encoded: false,\n log,\n });\n\n delegatedResolver.succeed(errorResponse);\n }\n\n //#endregion\n\n //#region Protected Methods\n\n /**\n * Get path from event with query strings\n *\n * @param event - The event sent by serverless\n */\n protected getPathFromEvent(event: ALBEvent): string {\n const path = this.stripPathFn(event.path);\n\n const queryParams = event.headers\n ? event.queryStringParameters\n : event.multiValueQueryStringParameters;\n\n return getPathWithQueryStringParams(path, queryParams || {});\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { APIGatewayProxyResult, Context } from 'aws-lambda';\nimport type { APIGatewayProxyEvent } from 'aws-lambda/trigger/api-gateway-proxy';\nimport type {\n AdapterContract,\n AdapterRequest,\n GetResponseAdapterProps,\n OnErrorProps,\n} from '../../contracts';\nimport { keysToLowercase } from '../../core';\nimport {\n type StripBasePathFn,\n buildStripBasePath,\n getDefaultIfUndefined,\n getEventBodyAsBuffer,\n getMultiValueHeadersMap,\n getPathWithQueryStringParams,\n} from '../../core';\n\n//#endregion\n\n/**\n * The options to customize the {@link ApiGatewayV1Adapter}\n *\n * @breadcrumb Adapters / AWS / ApiGatewayV1Adapter\n * @public\n */\nexport interface ApiGatewayV1Options {\n /**\n * Strip base path for custom domains\n *\n * @defaultValue ''\n */\n stripBasePath?: string;\n\n /**\n * Throw an exception when you send the `transfer-encoding=chunked`, currently, API Gateway doesn't support chunked transfer.\n * If this is set to `false`, we will remove the `transfer-encoding` header from the response and buffer the response body\n * while we remove the special characters inserted by the chunked encoding.\n *\n * @remarks To learn more https://github.com/H4ad/serverless-adapter/issues/165\n * @defaultValue true\n */\n throwOnChunkedTransferEncoding?: boolean;\n\n /**\n * Emulates the behavior of Node.js `http` module by ensuring all request headers are lowercase.\n *\n * @defaultValue false\n */\n lowercaseRequestHeaders?: boolean;\n}\n\n/**\n * The adapter to handle requests from AWS Api Gateway V1\n *\n * As per {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html | know issues}, we throw an exception when you send the `transfer-encoding=chunked`, currently, API Gateway doesn't support chunked transfer.\n *\n * @remarks This adapter is not fully compatible with \\@vendia/serverless-express, on \\@vendia they filter `transfer-encoding=chunked` but we throw an exception.\n *\n * @example\n * ```typescript\n * const stripBasePath = '/any/custom/base/path'; // default ''\n * const adapter = new ApiGatewayV1Adapter({ stripBasePath });\n * ```\n *\n * {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html | Event Reference}\n *\n * @breadcrumb Adapters / AWS / ApiGatewayV1Adapter\n * @public\n */\nexport class ApiGatewayV1Adapter\n implements\n AdapterContract<APIGatewayProxyEvent, Context, APIGatewayProxyResult>\n{\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link ApiGatewayV1Adapter}\n */\n constructor(protected readonly options?: ApiGatewayV1Options) {\n this.stripPathFn = buildStripBasePath(this.options?.stripBasePath);\n }\n\n //#endregion\n\n //#region Protected Properties\n\n /**\n * Strip base path function\n */\n protected stripPathFn: StripBasePathFn;\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public getAdapterName(): string {\n return ApiGatewayV1Adapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public canHandle(event: unknown): event is APIGatewayProxyEvent {\n const partialEventV1 = event as Partial<APIGatewayProxyEvent> & {\n version?: '2.0';\n };\n\n return !!(\n partialEventV1?.requestContext &&\n partialEventV1.version !== '2.0' &&\n partialEventV1.headers &&\n partialEventV1.multiValueHeaders &&\n ((partialEventV1.queryStringParameters === null &&\n partialEventV1.multiValueQueryStringParameters === null) ||\n (partialEventV1.queryStringParameters &&\n partialEventV1.multiValueQueryStringParameters))\n );\n }\n\n /**\n * {@inheritDoc}\n */\n public getRequest(event: APIGatewayProxyEvent): AdapterRequest {\n const method = event.httpMethod;\n const headers = this.options?.lowercaseRequestHeaders\n ? keysToLowercase(event.headers)\n : { ...event.headers };\n\n for (const multiValueHeaderKey of Object.keys(\n event.multiValueHeaders || {},\n )) {\n const headerValue = event.multiValueHeaders[multiValueHeaderKey];\n\n // event.headers by default only stick with first value if they see multiple headers\n // the other values will only appear on multiValueHeaderKey, in this case\n // we look for headers with more than 1 length which is the wrong values on event.headers\n // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html\n if (!headerValue || headerValue?.length <= 1) continue;\n\n headers[multiValueHeaderKey] = headerValue.join(',');\n }\n\n const path = this.getPathFromEvent(event);\n\n let body: Buffer | undefined;\n\n if (event.body) {\n const [bufferBody, contentLength] = getEventBodyAsBuffer(\n event.body,\n event.isBase64Encoded,\n );\n\n body = bufferBody;\n // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n headers['content-length'] = contentLength + '';\n }\n\n const remoteAddress = event.requestContext.identity.sourceIp;\n\n return {\n method,\n headers,\n body,\n remoteAddress,\n path,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public getResponse({\n headers: responseHeaders,\n body,\n isBase64Encoded,\n statusCode,\n response,\n }: GetResponseAdapterProps<APIGatewayProxyEvent>): APIGatewayProxyResult {\n const multiValueHeaders = getMultiValueHeadersMap(responseHeaders);\n\n const shouldThrowOnChunkedTransferEncoding = getDefaultIfUndefined(\n this.options?.throwOnChunkedTransferEncoding,\n true,\n );\n const transferEncodingHeader = multiValueHeaders['transfer-encoding'];\n const hasTransferEncodingChunked = transferEncodingHeader?.some(value =>\n value.includes('chunked'),\n );\n\n if (hasTransferEncodingChunked || response?.chunkedEncoding) {\n if (shouldThrowOnChunkedTransferEncoding) {\n throw new Error(\n 'chunked encoding in headers is not supported by API Gateway V1',\n );\n } else delete multiValueHeaders['transfer-encoding'];\n }\n\n return {\n statusCode,\n body,\n multiValueHeaders,\n isBase64Encoded,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public onErrorWhileForwarding({\n error,\n delegatedResolver,\n respondWithErrors,\n event,\n log,\n }: OnErrorProps<APIGatewayProxyEvent, APIGatewayProxyResult>): void {\n const body = respondWithErrors ? error.stack : '';\n const errorResponse = this.getResponse({\n event,\n statusCode: 500,\n body: body || '',\n headers: {},\n isBase64Encoded: false,\n log,\n });\n\n delegatedResolver.succeed(errorResponse);\n }\n\n //#endregion\n\n //#region Protected Methods\n\n /**\n * Get path from event with query strings\n *\n * @param event - The event sent by serverless\n */\n protected getPathFromEvent(event: APIGatewayProxyEvent): string {\n const path = this.stripPathFn(event.path);\n const queryParams = event.multiValueQueryStringParameters || {};\n\n if (event.queryStringParameters) {\n for (const queryStringKey of Object.keys(event.queryStringParameters)) {\n const queryStringValue = event.queryStringParameters[queryStringKey];\n\n if (queryStringValue === undefined) continue;\n\n if (!Array.isArray(queryParams[queryStringKey]))\n queryParams[queryStringKey] = [];\n\n if (queryParams[queryStringKey]!.includes(queryStringValue)) continue;\n\n queryParams[queryStringKey]!.push(queryStringValue);\n }\n }\n\n return getPathWithQueryStringParams(path, queryParams);\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { APIGatewayProxyEventV2, Context } from 'aws-lambda';\nimport type { APIGatewayProxyStructuredResultV2 } from 'aws-lambda/trigger/api-gateway-proxy';\nimport type {\n AdapterContract,\n AdapterRequest,\n GetResponseAdapterProps,\n OnErrorProps,\n} from '../../contracts';\nimport {\n type StripBasePathFn,\n buildStripBasePath,\n getDefaultIfUndefined,\n getEventBodyAsBuffer,\n getFlattenedHeadersMapAndCookies,\n getPathWithQueryStringParams,\n} from '../../core';\n\n//#endregion\n\n/**\n * The options to customize the {@link ApiGatewayV2Adapter}\n *\n * @breadcrumb Adapters / AWS / ApiGatewayV2Adapter\n * @public\n */\nexport interface ApiGatewayV2Options {\n /**\n * Strip base path for custom domains\n *\n * @defaultValue ''\n */\n stripBasePath?: string;\n\n /**\n * Throw an exception when you send the `transfer-encoding=chunked`, currently, API Gateway doesn't support chunked transfer.\n * If this is set to `false`, we will remove the `transfer-encoding` header from the response and buffer the response body\n * while we remove the special characters inserted by the chunked encoding.\n *\n * @remarks To learn more https://github.com/H4ad/serverless-adapter/issues/165\n * @defaultValue true\n */\n throwOnChunkedTransferEncoding?: boolean;\n}\n\n/**\n * The adapter to handle requests from AWS Api Gateway V2\n *\n * As per {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html | know issues}, we throw an exception when you send the `transfer-encoding=chunked`.\n * But, if you use this adapter to accept requests from Function URL, you can accept the `transfer-encoding=chunked` changing the method of invocation from `BUFFERED` to `RESPONSE_STREAM`.\n *\n * @example\n * ```typescript\n * const stripBasePath = '/any/custom/base/path'; // default ''\n * const adapter = new ApiGatewayV2Adapter({ stripBasePath });\n * ```\n *\n * {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html | Event Reference}\n *\n * @breadcrumb Adapters / AWS / ApiGatewayV2Adapter\n * @public\n */\nexport class ApiGatewayV2Adapter\n implements\n AdapterContract<\n APIGatewayProxyEventV2,\n Context,\n APIGatewayProxyStructuredResultV2\n >\n{\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link ApiGatewayV2Adapter}\n */\n constructor(protected readonly options?: ApiGatewayV2Options) {\n this.stripPathFn = buildStripBasePath(this.options?.stripBasePath);\n }\n\n //#endregion\n\n //#region Protected Properties\n\n /**\n * Strip base path function\n */\n protected stripPathFn: StripBasePathFn;\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public getAdapterName(): string {\n return ApiGatewayV2Adapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public canHandle(event: unknown): event is APIGatewayProxyEventV2 {\n const apiGatewayEvent = event as Partial<APIGatewayProxyEventV2> & {\n version?: string;\n };\n\n return !!(\n apiGatewayEvent?.requestContext && apiGatewayEvent.version === '2.0'\n );\n }\n\n /**\n * {@inheritDoc}\n */\n public getRequest(event: APIGatewayProxyEventV2): AdapterRequest {\n const method = event.requestContext.http.method;\n const path = this.getPathFromEvent(event);\n // accords https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html\n // all headers are lowercased and cannot be array\n // so no need to format, just a shallow copy will work here\n const headers = { ...event.headers };\n\n if (event.cookies) headers.cookie = event.cookies.join('; ');\n\n let body: Buffer | undefined;\n\n if (event.body) {\n const [bufferBody, contentLength] = getEventBodyAsBuffer(\n event.body,\n event.isBase64Encoded,\n );\n\n body = bufferBody;\n // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n headers['content-length'] = contentLength + '';\n }\n\n const remoteAddress = event.requestContext.http.sourceIp;\n\n return {\n method,\n headers,\n body,\n remoteAddress,\n path,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public getResponse({\n headers: responseHeaders,\n body,\n isBase64Encoded,\n statusCode,\n response,\n }: GetResponseAdapterProps<APIGatewayProxyEventV2>): APIGatewayProxyStructuredResultV2 {\n const { cookies, headers } =\n getFlattenedHeadersMapAndCookies(responseHeaders);\n\n const shouldThrowOnChunkedTransferEncoding = getDefaultIfUndefined(\n this.options?.throwOnChunkedTransferEncoding,\n true,\n );\n\n const transferEncodingHeader: string | undefined =\n headers['transfer-encoding'];\n\n const hasTransferEncodingChunked =\n transferEncodingHeader && transferEncodingHeader.includes('chunked');\n\n if (hasTransferEncodingChunked || response?.chunkedEncoding) {\n if (shouldThrowOnChunkedTransferEncoding) {\n throw new Error(\n 'chunked encoding in headers is not supported by API Gateway V2',\n );\n } else delete headers['transfer-encoding'];\n }\n\n return {\n statusCode,\n body,\n headers,\n isBase64Encoded,\n cookies,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public onErrorWhileForwarding({\n error,\n delegatedResolver,\n respondWithErrors,\n event,\n log,\n }: OnErrorProps<\n APIGatewayProxyEventV2,\n APIGatewayProxyStructuredResultV2\n >): void {\n const body = respondWithErrors ? error.stack : '';\n const errorResponse = this.getResponse({\n event,\n statusCode: 500,\n body: body || '',\n headers: {},\n isBase64Encoded: false,\n log,\n });\n\n delegatedResolver.succeed(errorResponse);\n }\n\n //#endregion\n\n //#region Protected Methods\n\n /**\n * Get path from event with query strings\n *\n * @param event - The event sent by serverless\n */\n protected getPathFromEvent(event: APIGatewayProxyEventV2): string {\n const path = this.stripPathFn(event.rawPath);\n const queryParams = event.rawQueryString;\n\n return getPathWithQueryStringParams(path, queryParams || {});\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { Context, SQSBatchItemFailure } from 'aws-lambda';\nimport type {\n AdapterContract,\n AdapterRequest,\n GetResponseAdapterProps,\n OnErrorProps,\n} from '../../../contracts';\nimport {\n EmptyResponse,\n type IEmptyResponse,\n getEventBodyAsBuffer,\n} from '../../../core';\n\n//#endregion\n\n/**\n * The options to customize the {@link AwsSimpleAdapter}\n *\n * @breadcrumb Adapters / AWS / AWS Simple Adapter\n * @public\n */\nexport interface AWSSimpleAdapterOptions {\n /**\n * The path that will be used to create a request to be forwarded to the framework.\n */\n forwardPath: string;\n\n /**\n * The http method that will be used to create a request to be forwarded to the framework.\n */\n forwardMethod: string;\n\n /**\n * The AWS Service host that will be injected inside headers to developer being able to validate if request originate from the library.\n */\n host: string;\n\n /**\n * Tells if this adapter should support batch item failures.\n */\n batch?: true | false;\n}\n\n/**\n * The batch item failure response expected from the API server\n *\n * @breadcrumb Adapters / AWS / AWS Simple Adapter\n * @public\n */\nexport type BatchItemFailureResponse = SQSBatchItemFailure;\n\n/**\n * The possible options of response for {@link AwsSimpleAdapter}\n *\n * @breadcrumb Adapters / AWS / AWS Simple Adapter\n * @public\n */\nexport type AWSSimpleAdapterResponseType =\n | BatchItemFailureResponse\n | IEmptyResponse;\n\n/**\n * The abstract adapter to use to implement other simple AWS adapters\n *\n * @breadcrumb Adapters / AWS / AWS Simple Adapter\n * @public\n */\nexport abstract class AwsSimpleAdapter<TEvent>\n implements AdapterContract<TEvent, Context, AWSSimpleAdapterResponseType>\n{\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link AwsSimpleAdapter}\n */\n constructor(protected readonly options: AWSSimpleAdapterOptions) {}\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public getAdapterName(): string {\n throw new Error('not implemented.');\n }\n\n /**\n * {@inheritDoc}\n */\n public canHandle(_: unknown): _ is TEvent {\n throw new Error('not implemented.');\n }\n\n /**\n * {@inheritDoc}\n */\n public getRequest(event: TEvent): AdapterRequest {\n const path = this.options.forwardPath;\n const method = this.options.forwardMethod;\n\n const [body, contentLength] = getEventBodyAsBuffer(\n JSON.stringify(event),\n false,\n );\n\n const headers = {\n host: this.options.host,\n 'content-type': 'application/json',\n 'content-length': String(contentLength),\n };\n\n return {\n method,\n headers,\n body,\n path,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public getResponse({\n body,\n headers,\n isBase64Encoded,\n event,\n statusCode,\n }: GetResponseAdapterProps<TEvent>): AWSSimpleAdapterResponseType {\n if (this.hasInvalidStatusCode(statusCode)) {\n throw new Error(\n JSON.stringify({ body, headers, isBase64Encoded, event, statusCode }),\n );\n }\n\n if (!this.options.batch) return EmptyResponse;\n\n if (isBase64Encoded) {\n throw new Error(\n 'SERVERLESS_ADAPTER: The response could not be base64 encoded when you set batch: true, the response should be a JSON.',\n );\n }\n\n if (!body) return EmptyResponse;\n\n return JSON.parse(body);\n }\n\n /**\n * {@inheritDoc}\n */\n public onErrorWhileForwarding({\n error,\n delegatedResolver,\n }: OnErrorProps<TEvent, AWSSimpleAdapterResponseType>): void {\n delegatedResolver.fail(error);\n }\n\n //#endregion\n\n //#region Protected Methods\n\n /**\n * Check if the status code is invalid\n *\n * @param statusCode - The status code\n */\n protected hasInvalidStatusCode(statusCode: number): boolean {\n return statusCode < 200 || statusCode >= 400;\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { DynamoDBStreamEvent } from 'aws-lambda';\nimport { getDefaultIfUndefined } from '../../core';\nimport { type AWSSimpleAdapterOptions, AwsSimpleAdapter } from './base/index';\n\n//#endregion\n\n/**\n * The options to customize the {@link DynamoDBAdapter}\n *\n * @breadcrumb Adapters / AWS / DynamoDBAdapter\n * @public\n */\nexport interface DynamoDBAdapterOptions\n extends Pick<AWSSimpleAdapterOptions, 'batch'> {\n /**\n * The path that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue /dynamo\n */\n dynamoDBForwardPath?: string;\n\n /**\n * The http method that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue POST\n */\n dynamoDBForwardMethod?: string;\n}\n\n/**\n * The adapter to handle requests from AWS DynamoDB.\n *\n * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error.\n *\n * {@link https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html | Event Reference}\n *\n * @example\n * ```typescript\n * const dynamoDBForwardPath = '/your/route/dynamo'; // default /dynamo\n * const dynamoDBForwardMethod = 'POST'; // default POST\n * const adapter = new DynamoDBAdapter({ dynamoDBForwardPath, dynamoDBForwardMethod });\n * ```\n *\n * @breadcrumb Adapters / AWS / DynamoDBAdapter\n * @public\n */\nexport class DynamoDBAdapter extends AwsSimpleAdapter<DynamoDBStreamEvent> {\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link DynamoDBAdapter}\n */\n constructor(options?: DynamoDBAdapterOptions) {\n super({\n forwardPath: getDefaultIfUndefined(\n options?.dynamoDBForwardPath,\n '/dynamo',\n ),\n forwardMethod: getDefaultIfUndefined(\n options?.dynamoDBForwardMethod,\n 'POST',\n ),\n batch: options?.batch,\n host: 'dynamodb.amazonaws.com',\n });\n }\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public override getAdapterName(): string {\n return DynamoDBAdapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public override canHandle(event: unknown): event is DynamoDBStreamEvent {\n const dynamoDBevent = event as Partial<DynamoDBStreamEvent>;\n\n if (!Array.isArray(dynamoDBevent?.Records)) return false;\n\n const eventSource = dynamoDBevent.Records[0]?.eventSource;\n\n return eventSource === 'aws:dynamodb';\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { EventBridgeEvent } from 'aws-lambda';\nimport { getDefaultIfUndefined } from '../../core';\nimport { AwsSimpleAdapter } from './base';\n\n//#endregion\n\n/**\n * The options to customize the {@link EventBridgeAdapter}\n *\n * @breadcrumb Adapters / AWS / EventBridgeAdapter\n * @public\n */\nexport interface EventBridgeOptions {\n /**\n * The path that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue /eventbridge\n */\n eventBridgeForwardPath?: string;\n\n /**\n * The http method that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue POST\n */\n eventBridgeForwardMethod?: string;\n}\n\n/**\n * Just a type alias to ignore generic types in the event\n *\n * @breadcrumb Adapters / AWS / EventBridgeAdapter\n * @public\n */\nexport type EventBridgeEventAll = EventBridgeEvent<any, any>;\n\n/**\n * The adapter to handle requests from AWS EventBridge (Cloudwatch Events).\n *\n * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error.\n *\n * {@link https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html | Event Reference}\n *\n * @example\n * ```typescript\n * const eventBridgeForwardPath = '/your/route/eventbridge'; // default /eventbridge\n * const eventBridgeForwardMethod = 'POST'; // default POST\n * const adapter = new EventBridgeAdapter({ eventBridgeForwardPath, eventBridgeForwardMethod });\n * ```\n *\n * @breadcrumb Adapters / AWS / EventBridgeAdapter\n * @public\n */\nexport class EventBridgeAdapter extends AwsSimpleAdapter<EventBridgeEventAll> {\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link EventBridgeAdapter}\n */\n constructor(options?: EventBridgeOptions) {\n super({\n forwardPath: getDefaultIfUndefined(\n options?.eventBridgeForwardPath,\n '/eventbridge',\n ),\n forwardMethod: getDefaultIfUndefined(\n options?.eventBridgeForwardMethod,\n 'POST',\n ),\n batch: false,\n host: 'events.amazonaws.com',\n });\n }\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public override getAdapterName(): string {\n return EventBridgeAdapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public override canHandle(event: unknown): event is EventBridgeEventAll {\n const eventBridgeEvent = event as Partial<EventBridgeEventAll>;\n\n // thanks to @cnuss in https://github.com/vendia/serverless-express/blob/b5da6070b8dd2fb674c1f7035dd7edfef1dc83a2/src/event-sources/utils.js#L87\n return !!(\n eventBridgeEvent &&\n eventBridgeEvent.version &&\n eventBridgeEvent.version === '0' &&\n eventBridgeEvent.id &&\n eventBridgeEvent['detail-type'] &&\n eventBridgeEvent.source &&\n eventBridgeEvent.account &&\n eventBridgeEvent.time &&\n eventBridgeEvent.region &&\n eventBridgeEvent.resources &&\n Array.isArray(eventBridgeEvent.resources) &&\n eventBridgeEvent.detail &&\n typeof eventBridgeEvent.detail === 'object' &&\n !Array.isArray(eventBridgeEvent.detail)\n );\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { CloudFrontRequest, Context } from 'aws-lambda';\nimport type {\n CloudFrontEvent,\n CloudFrontHeaders,\n CloudFrontResultResponse,\n} from 'aws-lambda/common/cloudfront';\nimport type {\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n} from 'aws-lambda/trigger/cloudfront-request';\nimport type {\n BothValueHeaders,\n Concrete,\n SingleValueHeaders,\n} from '../../@types';\nimport type {\n AdapterContract,\n AdapterRequest,\n GetResponseAdapterProps,\n OnErrorProps,\n} from '../../contracts';\nimport {\n getDefaultIfUndefined,\n getEventBodyAsBuffer,\n getPathWithQueryStringParams,\n} from '../../core';\n\n//#endregion\n\n/**\n * The type alias to indicate where we get the default value of query string to create the request.\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter\n * @public\n */\nexport type DefaultQueryString =\n CloudFrontRequestEvent['Records'][number]['cf']['request']['querystring'];\n\n/**\n * The type alias to indicate where we get the default value of path to create the request.\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter\n * @public\n */\nexport type DefaultForwardPath =\n CloudFrontRequestEvent['Records'][number]['cf']['request']['uri'];\n\n/**\n * Represents the body of the new version of Lambda\\@edge, which uses the `body` property inside `request` as the body (library) of the request.\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter\n * @public\n */\nexport type NewLambdaEdgeBody =\n CloudFrontRequestEvent['Records'][number]['cf']['request']['body'];\n\n/**\n * Represents the body of the old version of Lambda\\@edge supported by \\@vendia/serverless-express which returns the `data` property within `body` for the body (library) of the request.\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter\n * @public\n */\nexport type OldLambdaEdgeBody = Concrete<\n CloudFrontRequestEvent['Records'][number]['cf']['request']\n>['body']['data'];\n\n/**\n * The list was created based on {@link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-restrictions.html | these docs} in the \"Disallowed Headers\" section.\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter / Constants\n * @public\n */\nexport const DEFAULT_LAMBDA_EDGE_DISALLOWED_HEADERS: (string | RegExp)[] = [\n 'Connection',\n 'Expect',\n 'Keep-Alive',\n 'Proxy-Authenticate',\n 'Proxy-Authorization',\n 'Proxy-Connection',\n 'Trailer',\n 'Upgrade',\n 'X-Accel-Buffering',\n 'X-Accel-Charset',\n 'X-Accel-Limit-Rate',\n 'X-Accel-Redirect',\n /(X-Amz-Cf-)(.*)/gim,\n 'X-Cache',\n /(X-Edge-)(.*)/gim,\n 'X-Forwarded-Proto',\n 'X-Real-IP',\n];\n\n/**\n * The default max response size in bytes of viewer request and viewer response.\n *\n * @defaultValue 1024 * 40 = 40960 = 40KB\n *\n * {@link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html | Reference}\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter / Constants\n * @public\n */\nexport const DEFAULT_VIEWER_MAX_RESPONSE_SIZE_IN_BYTES = 1024 * 40;\n\n/**\n * The default max response size in bytes of origin request and origin response.\n *\n * @defaultValue 1024 * 1024 = 1048576 = 1MB\n *\n * {@link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html | Reference}\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter / Constants\n * @public\n */\nexport const DEFAULT_ORIGIN_MAX_RESPONSE_SIZE_IN_BYTES = 1024 * 1024;\n\n/**\n * The options to customize the {@link LambdaEdgeAdapter}.\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter\n * @public\n */\nexport interface LambdaEdgeAdapterOptions {\n /**\n * The max response size in bytes of viewer request and viewer response.\n *\n * {@link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html | Reference}\n *\n * @defaultValue {@link DEFAULT_VIEWER_MAX_RESPONSE_SIZE_IN_BYTES}\n */\n viewerMaxResponseSizeInBytes?: number;\n\n /**\n * The max response size in bytes of origin request and origin response.\n *\n * {@link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html | Reference}\n *\n * @defaultValue {@link DEFAULT_ORIGIN_MAX_RESPONSE_SIZE_IN_BYTES}\n */\n originMaxResponseSizeInBytes?: number;\n\n /**\n * The function called when the response size exceed the max limits of the Lambda\\@edge\n *\n * @param response - The response from framework that exceed the limit of Lambda\\@edge\n * @defaultValue undefined\n */\n onResponseSizeExceedLimit?: (\n response: CloudFrontRequestResult,\n ) => CloudFrontRequestResult;\n\n /**\n * Return the path to be used to create a request to the framework\n *\n * @remarks You MUST append the query params from {@link DefaultQueryString}, you can use the helper {@link getPathWithQueryStringParams}.\n *\n * @param event - The event sent by the serverless\n * @defaultValue The value from {@link DefaultForwardPath}\n */\n getPathFromEvent?: (\n event: CloudFrontRequestEvent['Records'][number],\n ) => string;\n\n /**\n * The headers that will be stripped from the headers object because Lambda\\@edge will fail if these headers are passed in the response.\n *\n * @remarks All headers will be compared with other headers using toLowerCase, but for the RegExp, if you modify this list, you must put the flag `/gmi` at the end of the RegExp (ex: `/(X-Amz-Cf-)(.*)/gim`)\n *\n * @defaultValue To get the full list, see {@link DEFAULT_LAMBDA_EDGE_DISALLOWED_HEADERS}.\n */\n disallowedHeaders?: (string | RegExp)[];\n\n /**\n * If you want to change how we check against the header if it should be stripped, you can pass a function to this property.\n *\n * @param header - The header of the response\n * @defaultValue The default method is implemented to test the header against the list {@link LambdaEdgeAdapterOptions.disallowedHeaders}.\n */\n shouldStripHeader?: (header: string) => boolean;\n\n /**\n * By default, the {@link aws-lambda#CloudFrontRequestResult} has the `headers` property, but we also have the headers sent by the framework too.\n * So this setting tells us how to handle this case, if you pass `true` to this property, we will use the framework headers.\n * Otherwise, we will forward the body back to cloudfront without modifying or trying to set the `headers` property inside {@link aws-lambda#CloudFrontRequestResult}.\n *\n * @defaultValue false\n */\n shouldUseHeadersFromFramework?: boolean;\n}\n\n/**\n * The adapter to handle requests from AWS Lambda\\@Edge.\n *\n * This adapter is not fully compatible with Lambda\\@edge supported by \\@vendia/serverless-express, the request body was modified to return {@link NewLambdaEdgeBody} instead {@link OldLambdaEdgeBody}.\n * Also, the response has been modified to return entire body sent by the framework, in this form you MUST return the body from the framework in the format of {@link aws-lambda#CloudFrontRequestResult}.\n * And when we get an error during the forwarding to the framework, we call `resolver.fail` instead of trying to return status 500 like the old implementation was.\n *\n * {@link https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html | Lambda edge docs}\n * {@link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html | Event Reference}\n *\n * @example\n * ```typescript\n * const getPathFromEvent = () => '/lambda/edge'; // will forward all requests to the same endpoint\n * const adapter = new LambdaEdgeAdapter({ getPathFromEvent });\n * ```\n *\n * @breadcrumb Adapters / AWS / LambdaEdgeAdapter\n * @public\n */\nexport class LambdaEdgeAdapter\n implements\n AdapterContract<CloudFrontRequestEvent, Context, CloudFrontRequestResult>\n{\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link LambdaEdgeAdapter}\n */\n constructor(protected readonly options?: LambdaEdgeAdapterOptions) {\n const disallowedHeaders = getDefaultIfUndefined(\n this.options?.disallowedHeaders,\n DEFAULT_LAMBDA_EDGE_DISALLOWED_HEADERS,\n );\n\n this.cachedDisallowedHeaders = disallowedHeaders.map(disallowedHeader => {\n if (disallowedHeader instanceof RegExp) return disallowedHeader;\n\n return new RegExp(`(${disallowedHeader})`, 'gim');\n });\n }\n\n //#endregion\n\n //#region Protected Properties\n\n /**\n * This property is used to cache the disallowed headers in `RegExp` version, even if you provide a string in `disallowedHeader`, we will cache it in an instance of `RegExp`.\n */\n protected readonly cachedDisallowedHeaders: RegExp[];\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public getAdapterName(): string {\n return LambdaEdgeAdapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public canHandle(event: unknown): event is CloudFrontRequestEvent {\n const lambdaEdgeEvent = event as Partial<CloudFrontRequestEvent>;\n\n if (!Array.isArray(lambdaEdgeEvent?.Records)) return false;\n\n const eventType = lambdaEdgeEvent.Records[0]?.cf?.config?.eventType;\n const validEventTypes: CloudFrontEvent['config']['eventType'][] = [\n 'origin-response',\n 'origin-request',\n 'viewer-response',\n 'viewer-request',\n ];\n\n return validEventTypes.includes(eventType);\n }\n\n /**\n * {@inheritDoc}\n */\n public getRequest(event: CloudFrontRequestEvent): AdapterRequest {\n const request = event.Records[0];\n const cloudFrontRequest = request.cf.request;\n\n const method = cloudFrontRequest.method;\n\n const pathFromOptions = this.options?.getPathFromEvent\n ? this.options.getPathFromEvent(request)\n : undefined;\n const defaultPath = getPathWithQueryStringParams(\n cloudFrontRequest.uri,\n cloudFrontRequest.querystring,\n );\n const path = getDefaultIfUndefined(pathFromOptions, defaultPath);\n\n const remoteAddress = cloudFrontRequest.clientIp;\n\n const headers =\n this.getFlattenedHeadersFromCloudfrontRequest(cloudFrontRequest);\n\n let body: Buffer | undefined;\n\n if (cloudFrontRequest.body) {\n const [buffer, contentLength] = getEventBodyAsBuffer(\n JSON.stringify(cloudFrontRequest.body),\n false,\n );\n\n body = buffer;\n headers['content-length'] = contentLength.toString();\n }\n\n const { host } = headers;\n\n return {\n method,\n path,\n headers,\n body,\n remoteAddress,\n host,\n hostname: host,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public getResponse(\n props: GetResponseAdapterProps<CloudFrontRequestEvent>,\n ): CloudFrontRequestResult {\n const response = this.getResponseToLambdaEdge(props);\n const responseToServiceBytes = new TextEncoder().encode(\n JSON.stringify(response),\n ).length;\n\n const isOriginRequestOrResponse = this.isEventTypeOrigin(\n props.event.Records[0].cf.config,\n );\n const maxSizeInBytes = isOriginRequestOrResponse\n ? getDefaultIfUndefined(\n this.options?.originMaxResponseSizeInBytes,\n DEFAULT_ORIGIN_MAX_RESPONSE_SIZE_IN_BYTES,\n )\n : getDefaultIfUndefined(\n this.options?.viewerMaxResponseSizeInBytes,\n DEFAULT_VIEWER_MAX_RESPONSE_SIZE_IN_BYTES,\n );\n\n if (responseToServiceBytes <= maxSizeInBytes) return response;\n\n if (this.options?.onResponseSizeExceedLimit)\n this.options.onResponseSizeExceedLimit(response);\n else {\n props.log.error(\n `SERVERLESS_ADAPTER:LAMBDA_EDGE_ADAPTER: Max response size exceeded: ${responseToServiceBytes} of the max of ${maxSizeInBytes}.`,\n );\n }\n\n return response;\n }\n\n /**\n * {@inheritDoc}\n */\n public onErrorWhileForwarding({\n error,\n delegatedResolver,\n }: OnErrorProps<CloudFrontRequestEvent, CloudFrontRequestResult>): void {\n delegatedResolver.fail(error);\n }\n\n //#endregion\n\n //#region Protected Methods\n\n /**\n * Returns the headers with the flattened (non-list) values of the cloudfront request headers\n *\n * @param cloudFrontRequest - The cloudfront request\n */\n protected getFlattenedHeadersFromCloudfrontRequest(\n cloudFrontRequest: CloudFrontRequest,\n ): SingleValueHeaders {\n return Object.keys(cloudFrontRequest.headers).reduce((acc, headerKey) => {\n const headerValue = cloudFrontRequest.headers[headerKey];\n\n acc[headerKey] = headerValue.map(header => header.value).join(',');\n\n return acc;\n }, {} as SingleValueHeaders);\n }\n\n /**\n * Returns the framework response in the format required by the Lambda\\@edge.\n *\n * @param body - The body of the response\n * @param frameworkHeaders - The headers from the framework\n */\n protected getResponseToLambdaEdge({\n body,\n headers: frameworkHeaders,\n }: GetResponseAdapterProps<CloudFrontRequestEvent>): CloudFrontRequestResult {\n const shouldUseHeadersFromFramework = getDefaultIfUndefined(\n this.options?.shouldUseHeadersFromFramework,\n false,\n );\n\n const parsedBody: CloudFrontResultResponse | CloudFrontRequest =\n JSON.parse(body);\n\n if (parsedBody.headers) {\n parsedBody.headers = Object.keys(parsedBody.headers).reduce(\n (acc, header) => {\n if (this.shouldStripHeader(header)) return acc;\n\n acc[header] = parsedBody.headers![header];\n\n return acc;\n },\n {} as CloudFrontHeaders,\n );\n }\n\n if (!shouldUseHeadersFromFramework) return parsedBody;\n\n parsedBody.headers = this.getHeadersForCloudfrontResponse(frameworkHeaders);\n\n return parsedBody;\n }\n\n /**\n * Returns headers in Cloudfront Response format.\n *\n * @param originalHeaders - The original version of the request sent by the framework\n */\n protected getHeadersForCloudfrontResponse(\n originalHeaders: BothValueHeaders,\n ): CloudFrontHeaders {\n return Object.keys(originalHeaders).reduce((acc, headerKey) => {\n if (this.shouldStripHeader(headerKey)) return acc;\n\n if (!acc[headerKey]) acc[headerKey] = [];\n\n const headerValue = originalHeaders[headerKey];\n\n if (!Array.isArray(headerValue)) {\n acc[headerKey].push({\n key: headerKey,\n value: headerValue || '',\n });\n\n return acc;\n }\n\n const headersArray = headerValue.map(value => ({\n key: headerKey,\n value: value,\n }));\n\n acc[headerKey].push(...headersArray);\n\n return acc;\n }, {} as CloudFrontHeaders);\n }\n\n /**\n * Returns the information if we should remove the response header\n *\n * @param headerKey - The header that will be tested\n */\n protected shouldStripHeader(headerKey: string): boolean {\n if (this.options?.shouldStripHeader)\n return this.options.shouldStripHeader(headerKey);\n\n const headerKeyLowerCase = headerKey.toLowerCase();\n\n for (const stripHeaderIf of this.cachedDisallowedHeaders) {\n if (!stripHeaderIf.test(headerKeyLowerCase)) continue;\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Determines whether the event is from origin or is from viewer.\n *\n * @param content - The event sent by AWS or the response sent by the framework\n */\n protected isEventTypeOrigin(content: CloudFrontEvent['config']): boolean {\n return content.eventType.includes('origin');\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { S3Event } from 'aws-lambda';\nimport { getDefaultIfUndefined } from '../../core';\nimport { AwsSimpleAdapter } from './base/index';\n\n//#endregion\n\n/**\n * The options to customize the {@link S3Adapter}\n *\n * @breadcrumb Adapters / AWS / S3Adapter\n * @public\n */\nexport interface S3AdapterOptions {\n /**\n * The path that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue /s3\n */\n s3ForwardPath?: string;\n\n /**\n * The http method that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue POST\n */\n s3ForwardMethod?: string;\n}\n\n/**\n * The adapter to handle requests from AWS S3.\n *\n * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error.\n *\n * {@link https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html | Event Reference}\n *\n * @example\n * ```typescript\n * const s3ForwardPath = '/your/route/s3'; // default /s3\n * const s3ForwardMethod = 'POST'; // default POST\n * const adapter = new S3Adapter({ s3ForwardPath, s3ForwardMethod });\n * ```\n *\n * @breadcrumb Adapters / AWS / S3Adapter\n * @public\n */\nexport class S3Adapter extends AwsSimpleAdapter<S3Event> {\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link SNSAdapter}\n */\n constructor(options?: S3AdapterOptions) {\n super({\n forwardPath: getDefaultIfUndefined(options?.s3ForwardPath, '/s3'),\n forwardMethod: getDefaultIfUndefined(options?.s3ForwardMethod, 'POST'),\n batch: false,\n host: 's3.amazonaws.com',\n });\n }\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public override getAdapterName(): string {\n return S3Adapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public override canHandle(event: unknown): event is S3Event {\n const s3Event = event as Partial<S3Event>;\n\n if (!Array.isArray(s3Event?.Records)) return false;\n\n const eventSource = s3Event.Records[0]?.eventSource;\n\n return eventSource === 'aws:s3';\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { SNSEvent } from 'aws-lambda';\nimport { getDefaultIfUndefined } from '../../core';\nimport { AwsSimpleAdapter } from './base';\n\n//#endregion\n\n/**\n * The options to customize the {@link SNSAdapter}\n *\n * @breadcrumb Adapters / AWS / SNSAdapter\n * @public\n */\nexport interface SNSAdapterOptions {\n /**\n * The path that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue /sns\n */\n snsForwardPath?: string;\n\n /**\n * The http method that will be used to create a request to be forwarded to the framework.\n *\n * @defaultValue POST\n */\n snsForwardMethod?: string;\n}\n\n/**\n * The adapter to handle requests from AWS SNS.\n *\n * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error.\n *\n * {@link https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html | Event Reference}\n *\n * @example\n * ```typescript\n * const snsForwardPath = '/your/route/sns'; // default /sns\n * const snsForwardMethod = 'POST'; // default POST\n * const adapter = new SNSAdapter({ snsForwardPath, snsForwardMethod });\n * ```\n *\n * @breadcrumb Adapters / AWS / SNSAdapter\n * @public\n */\nexport class SNSAdapter extends AwsSimpleAdapter<SNSEvent> {\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link SNSAdapter}\n */\n constructor(options?: SNSAdapterOptions) {\n super({\n forwardPath: getDefaultIfUndefined(options?.snsForwardPath, '/sns'),\n forwardMethod: getDefaultIfUndefined(options?.snsForwardMethod, 'POST'),\n batch: false,\n host: 'sns.amazonaws.com',\n });\n }\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public override getAdapterName(): string {\n return SNSAdapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public override canHandle(event: unknown): event is SNSEvent {\n const snsEvent = event as Partial<SNSEvent>;\n\n if (!Array.isArray(snsEvent?.Records)) return false;\n\n const eventSource = snsEvent.Records[0]?.EventSource;\n\n return eventSource === 'aws:sns';\n }\n\n //#endregion\n}\n","//#region Imports\n\nimport type { SQSEvent } from 'aws-lambda';\nimport { getDefaultIfUndefined } from '../../core';\nimport { type AWSSimpleAdapterOptions, AwsSimpleAdapter } from './base/index';\n\n//#endregion\n\n/**\n * The options to customize the {@link SQSAdapter}\n *\n *