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 12.1 kB
{"version":3,"sources":["../../../src/handlers/aws/aws-stream.handler.ts"],"sourcesContent":["//#region Imports\n\nimport { Writable } from 'node:stream';\nimport { inspect } from 'node:util';\nimport type { APIGatewayProxyEventV2, Context } from 'aws-lambda';\nimport type { APIGatewayProxyStructuredResultV2 } from 'aws-lambda/trigger/api-gateway-proxy';\nimport type { BinarySettings } from '../../@types';\nimport type {\n AdapterContract,\n AdapterRequest,\n FrameworkContract,\n ResolverContract,\n ServerlessHandler,\n} from '../../contracts';\nimport {\n BaseHandler,\n type ILogger,\n getFlattenedHeadersMap,\n setCurrentInvoke,\n waitForStreamComplete,\n} from '../../core';\nimport { ServerlessRequest, ServerlessStreamResponse } from '../../network';\n\n//#endregion\n\n/**\n * @breadcrumb Handlers / AwsStreamHandler\n * @public\n */\nexport type AWSResponseStream = Writable;\n\n/**\n * @breadcrumb Handlers / AwsStreamHandler\n * @public\n */\nexport type AWSStreamResponseMetadata = Pick<\n APIGatewayProxyStructuredResultV2,\n 'statusCode' | 'headers' | 'cookies'\n>;\n\n/**\n * @breadcrumb Handlers / AwsStreamHandler\n * @public\n */\ndeclare const awslambda: {\n streamifyResponse: (\n handler: (\n event: APIGatewayProxyEventV2,\n response: AWSResponseStream,\n context: Context,\n ) => Promise<void>,\n ) => any;\n HttpResponseStream: {\n from: (\n stream: AWSResponseStream,\n httpResponseMetadata: AWSStreamResponseMetadata,\n ) => AWSResponseStream;\n };\n};\n\n/**\n * The interface that customizes the {@link AwsStreamHandler}\n *\n * @breadcrumb Handlers / AwsStreamHandler\n * @public\n */\nexport type AwsStreamHandlerOptions = {\n /**\n * Set the value of the property `callbackWaitsForEmptyEventLoop`, you can set to `false` to fix issues with long execution due to not cleaning the event loop ([ref](https://github.com/H4ad/serverless-adapter/issues/264)).\n * In the next release, this value will be changed to `false`.\n *\n * @defaultValue undefined\n */\n callbackWaitsForEmptyEventLoop?: boolean;\n};\n\n/**\n * The interface that describes the internal context used by the {@link AwsStreamHandler}\n *\n * @breadcrumb Handlers / AwsStreamHandler\n * @public\n */\nexport type AWSStreamContext = {\n /**\n * The response stream provided by the serverless\n */\n response: AWSResponseStream;\n /**\n * The context provided by the serverless\n */\n context: Context;\n};\n\n/**\n * The class that implements a default serverless handler consisting of a function with event, context and callback parameters respectively\n *\n * @breadcrumb Handlers / AwsStreamHandler\n * @public\n */\nexport class AwsStreamHandler<TApp> extends BaseHandler<\n TApp,\n APIGatewayProxyEventV2,\n AWSStreamContext,\n void,\n AWSStreamResponseMetadata,\n void\n> {\n //#region Constructor\n\n /**\n * Construtor padrão\n */\n constructor(private readonly options?: AwsStreamHandlerOptions) {\n super();\n }\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public getHandler(\n app: TApp,\n framework: FrameworkContract<TApp>,\n adapters: AdapterContract<\n APIGatewayProxyEventV2,\n AWSStreamContext,\n AWSStreamResponseMetadata\n >[],\n _resolverFactory: ResolverContract<\n unknown,\n unknown,\n unknown,\n unknown,\n unknown\n >,\n binarySettings: BinarySettings,\n respondWithErrors: boolean,\n log: ILogger,\n ): ServerlessHandler<Promise<void>> {\n return awslambda.streamifyResponse(async (event, response, context) => {\n if (this.options?.callbackWaitsForEmptyEventLoop !== undefined) {\n // TODO(h4ad): Set the following property to false by default\n context.callbackWaitsForEmptyEventLoop =\n this.options.callbackWaitsForEmptyEventLoop;\n }\n\n const streamContext = { response, context };\n\n this.onReceiveRequest(\n log,\n event,\n streamContext,\n binarySettings,\n respondWithErrors,\n );\n\n const adapter = this.getAdapterByEventAndContext(\n event,\n streamContext,\n adapters,\n log,\n );\n\n this.onResolveAdapter(log, adapter);\n\n setCurrentInvoke({ event, context });\n\n await this.forwardRequestToFramework(\n app,\n framework,\n event,\n streamContext,\n adapter,\n binarySettings,\n log,\n );\n });\n }\n\n //#endregion\n\n //#region Hooks\n\n /**\n * The hook executed on receive a request, before the request is being processed\n *\n * @param log - The instance of logger\n * @param event - The event sent by serverless\n * @param context - The context sent by serverless\n * @param binarySettings - The binary settings\n * @param respondWithErrors - Indicates whether the error stack should be included in the response or not\n */\n protected onReceiveRequest(\n log: ILogger,\n event: APIGatewayProxyEventV2,\n context: AWSStreamContext,\n binarySettings: BinarySettings,\n respondWithErrors: boolean,\n ): void {\n log.debug('SERVERLESS_ADAPTER:PROXY', () => ({\n event,\n context: inspect(context, { depth: null }),\n binarySettings,\n respondWithErrors,\n }));\n }\n\n /**\n * The hook executed after resolve the adapter that will be used to handle the request and response\n *\n * @param log - The instance of logger\n * @param adapter - The adapter resolved\n */\n protected onResolveAdapter(\n log: ILogger,\n adapter: AdapterContract<\n APIGatewayProxyEventV2,\n AWSStreamContext,\n AWSStreamResponseMetadata\n >,\n ): void {\n log.debug(\n 'SERVERLESS_ADAPTER:RESOLVED_ADAPTER_NAME: ',\n adapter.getAdapterName(),\n );\n }\n\n /**\n * The hook executed after resolves the request values that will be sent to the framework\n *\n * @param log - The instance of logger\n * @param requestValues - The request values returned by the adapter\n */\n protected onResolveRequestValues(\n log: ILogger,\n requestValues: AdapterRequest,\n ): void {\n log.debug(\n 'SERVERLESS_ADAPTER:FORWARD_REQUEST_TO_FRAMEWORK:REQUEST_VALUES',\n () => ({\n requestValues: {\n ...requestValues,\n body: requestValues.body?.toString(),\n },\n }),\n );\n }\n\n /**\n * The hook executed before sending response to the serverless with response from adapter\n *\n * @param log - The instance of logger\n * @param successResponse - The success response resolved by the adapter\n */\n protected onForwardResponseAdapterResponse(\n log: ILogger,\n successResponse: AWSStreamResponseMetadata,\n ) {\n log.debug('SERVERLESS_ADAPTER:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE', {\n successResponse,\n });\n }\n\n //#endregion\n\n //#region Protected Methods\n\n /**\n * The function to forward the event to the framework\n *\n * @param app - The instance of the app (express, hapi, etc...)\n * @param framework - The framework that will process requests\n * @param event - The event sent by serverless\n * @param context - The context sent by serverless\n * @param adapter - The adapter resolved to this event\n * @param _binarySettings - The binary settings\n * @param log - The instance of logger\n */\n protected async forwardRequestToFramework(\n app: TApp,\n framework: FrameworkContract<TApp>,\n event: APIGatewayProxyEventV2,\n context: AWSStreamContext,\n adapter: AdapterContract<\n APIGatewayProxyEventV2,\n AWSStreamContext,\n AWSStreamResponseMetadata\n >,\n _binarySettings: BinarySettings,\n log: ILogger,\n ): Promise<void> {\n const requestValues = adapter.getRequest(event, context, log);\n\n this.onResolveRequestValues(log, requestValues);\n\n const request = new ServerlessRequest({\n method: requestValues.method,\n headers: requestValues.headers,\n body: requestValues.body,\n remoteAddress: requestValues.remoteAddress,\n url: requestValues.path,\n });\n\n const response = new ServerlessStreamResponse({\n method: requestValues.method,\n onReceiveHeaders: (status, headers) => {\n const flattenedHeaders = getFlattenedHeadersMap(headers);\n const awsMetadata: AWSStreamResponseMetadata = {\n statusCode: status,\n headers: flattenedHeaders,\n };\n\n const cookies = headers['set-cookie'];\n\n if (cookies) {\n awsMetadata.cookies = Array.isArray(cookies) ? cookies : [cookies];\n\n delete headers['set-cookie'];\n delete flattenedHeaders['set-cookie'];\n }\n\n this.onForwardResponseAdapterResponse(log, awsMetadata);\n\n const finalResponse = awslambda.HttpResponseStream.from(\n context.response,\n awsMetadata,\n );\n\n // We must call write with an empty string to trigger the awsMetadata to be sent\n // https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/2ce88619fd176a5823bc5f38c5484d1cbdf95717/src/HttpResponseStream.js#L22\n finalResponse.write('');\n\n return finalResponse;\n },\n log,\n });\n\n framework.sendRequest(app, request, response);\n\n log.debug(\n 'SERVERLESS_ADAPTER:FORWARD_REQUEST_TO_FRAMEWORK:WAITING_STREAM_COMPLETE',\n );\n await waitForStreamComplete(response);\n\n log.debug(\n 'SERVERLESS_ADAPTER:FORWARD_REQUEST_TO_FRAMEWORK:STREAM_COMPLETE',\n );\n context.response.end();\n }\n\n //#endregion\n}\n"],"mappings":"6HAEA,MAAyB,cACzB,OAAS,WAAAA,MAAe,YAgGjB,IAAMC,EAAN,cAAqCC,CAO1C,CAMA,YAA6BC,EAAmC,CAC9D,MAAM,EADqB,aAAAA,CAE7B,CAlHF,MA0GE,CAAAC,EAAA,yBAiBO,WACLC,EACAC,EACAC,EAKAC,EAOAC,EACAC,EACAC,EACkC,CAClC,OAAO,UAAU,kBAAkB,MAAOC,EAAOC,EAAUC,IAAY,CACjE,KAAK,SAAS,iCAAmC,SAEnDA,EAAQ,+BACN,KAAK,QAAQ,gCAGjB,IAAMC,EAAgB,CAAE,SAAAF,EAAU,QAAAC,CAAQ,EAE1C,KAAK,iBACHH,EACAC,EACAG,EACAN,EACAC,CACF,EAEA,IAAMM,EAAU,KAAK,4BACnBJ,EACAG,EACAR,EACAI,CACF,EAEA,KAAK,iBAAiBA,EAAKK,CAAO,EAElCC,EAAiB,CAAE,MAAAL,EAAO,QAAAE,CAAQ,CAAC,EAEnC,MAAM,KAAK,0BACTT,EACAC,EACAM,EACAG,EACAC,EACAP,EACAE,CACF,CACF,CAAC,CACH,CAeU,iBACRA,EACAC,EACAE,EACAL,EACAC,EACM,CACNC,EAAI,MAAM,2BAA4B,KAAO,CAC3C,MAAAC,EACA,QAASM,EAAQJ,EAAS,CAAE,MAAO,IAAK,CAAC,EACzC,eAAAL,EACA,kBAAAC,CACF,EAAE,CACJ,CAQU,iBACRC,EACAK,EAKM,CACNL,EAAI,MACF,6CACAK,EAAQ,eAAe,CACzB,CACF,CAQU,uBACRL,EACAQ,EACM,CACNR,EAAI,MACF,iEACA,KAAO,CACL,cAAe,CACb,GAAGQ,EACH,KAAMA,EAAc,MAAM,SAAS,CACrC,CACF,EACF,CACF,CAQU,iCACRR,EACAS,EACA,CACAT,EAAI,MAAM,4DAA6D,CACrE,gBAAAS,CACF,CAAC,CACH,CAiBA,MAAgB,0BACdf,EACAC,EACAM,EACAE,EACAE,EAKAK,EACAV,EACe,CACf,IAAMQ,EAAgBH,EAAQ,WAAWJ,EAAOE,EAASH,CAAG,EAE5D,KAAK,uBAAuBA,EAAKQ,CAAa,EAE9C,IAAMG,EAAU,IAAIC,EAAkB,CACpC,OAAQJ,EAAc,OACtB,QAASA,EAAc,QACvB,KAAMA,EAAc,KACpB,cAAeA,EAAc,cAC7B,IAAKA,EAAc,IACrB,CAAC,EAEKN,EAAW,IAAIW,EAAyB,CAC5C,OAAQL,EAAc,OACtB,iBAAkBf,EAAA,CAACqB,EAAQC,IAAY,CACrC,IAAMC,EAAmBC,EAAuBF,CAAO,EACjDG,EAAyC,CAC7C,WAAYJ,EACZ,QAASE,CACX,EAEMG,EAAUJ,EAAQ,YAAY,EAEhCI,IACFD,EAAY,QAAU,MAAM,QAAQC,CAAO,EAAIA,EAAU,CAACA,CAAO,EAEjE,OAAOJ,EAAQ,YAAY,EAC3B,OAAOC,EAAiB,YAAY,GAGtC,KAAK,iCAAiChB,EAAKkB,CAAW,EAEtD,IAAME,EAAgB,UAAU,mBAAmB,KACjDjB,EAAQ,SACRe,CACF,EAIA,OAAAE,EAAc,MAAM,EAAE,EAEfA,CACT,EA5BkB,oBA6BlB,IAAApB,CACF,CAAC,EAEDL,EAAU,YAAYD,EAAKiB,EAAST,CAAQ,EAE5CF,EAAI,MACF,yEACF,EACA,MAAMqB,EAAsBnB,CAAQ,EAEpCF,EAAI,MACF,iEACF,EACAG,EAAQ,SAAS,IAAI,CACvB,CAGF","names":["inspect","AwsStreamHandler","BaseHandler","options","__name","app","framework","adapters","_resolverFactory","binarySettings","respondWithErrors","log","event","response","context","streamContext","adapter","setCurrentInvoke","inspect","requestValues","successResponse","_binarySettings","request","ServerlessRequest","ServerlessStreamResponse","status","headers","flattenedHeaders","getFlattenedHeadersMap","awsMetadata","cookies","finalResponse","waitForStreamComplete"]}