From 9c7249f972067747f2cca217f1333949eab529f1 Mon Sep 17 00:00:00 2001 From: Nicolas Froidure Date: Tue, 22 Oct 2024 11:31:34 +0200 Subject: [PATCH] feat(@whook/aws-lambda): use the graceful shutdown feature When this module was created, AWS Lambda did not permit to graceful shutdown the running lambdas. Now that we can, simply injecting it. Also mordernized the code to use async/await. fix #189 --- packages/whook-aws-lambda/src/index.ts | 15 ++++++++++----- .../src/wrappers/awsConsumerLambda.ts | 10 ++-------- .../src/wrappers/awsCronLambda.ts | 10 +++------- .../src/wrappers/awsHTTPLambda.ts | 6 ++---- .../src/wrappers/awsKafkaConsumerLambda.ts | 14 ++++++-------- .../src/wrappers/awsLogSubscriberLambda.ts | 8 +------- .../whook-aws-lambda/src/wrappers/awsS3Lambda.ts | 8 ++------ .../src/wrappers/awsTransformerLambda.ts | 10 +++------- 8 files changed, 29 insertions(+), 52 deletions(-) diff --git a/packages/whook-aws-lambda/src/index.ts b/packages/whook-aws-lambda/src/index.ts index 94ac276a..572edb0d 100644 --- a/packages/whook-aws-lambda/src/index.ts +++ b/packages/whook-aws-lambda/src/index.ts @@ -317,7 +317,10 @@ async function buildAnyLambda( const srcRelativePath = relative(lambdaPath, srcPath); const initializerContent = ( - await buildInitializer([`OPERATION_HANDLER_${finalEntryPoint}`]) + await buildInitializer([ + `OPERATION_HANDLER_${finalEntryPoint}`, + 'process', + ]) ).replaceAll(pathToFileURL(srcPath).toString(), srcRelativePath); const indexContent = await buildLambdaIndex( `OPERATION_HANDLER_${finalEntryPoint}`, @@ -349,13 +352,15 @@ async function buildLambdaIndex(name: string): Promise { return `// Automatically generated by \`@whook/aws-lambda\` import { initialize } from './initialize.js'; -const initializationPromise = initialize(); +const services = await initialize(); -export default function handler (event, context, callback) { +export const handler = async (event, context) => { context.callbackWaitsForEmptyEventLoop = false; - return initializationPromise - .then(services => services['${name}'](event, context, callback)); + + return await services['${name}'](event, context); }; + +export default handler; `; } diff --git a/packages/whook-aws-lambda/src/wrappers/awsConsumerLambda.ts b/packages/whook-aws-lambda/src/wrappers/awsConsumerLambda.ts index 33792eec..e8e08887 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsConsumerLambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsConsumerLambda.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { autoService } from 'knifecycle'; import { noop } from '@whook/whook'; import { printStackTrace, YError } from 'yerror'; @@ -16,7 +15,6 @@ import type { KinesisStreamEvent, SQSEvent, SNSEvent, - Context, SESEvent, DynamoDBStreamEvent, } from 'aws-lambda'; @@ -84,7 +82,7 @@ async function initWrapHandlerForConsumerLambda({ const wrappedHandler = handleForAWSConsumerLambda.bind( null, { ENV, OPERATION_API, apm, time, log }, - handler as any, + handler as WhookHandler, ); return wrappedHandler as unknown as S; @@ -108,8 +106,6 @@ async function handleForAWSConsumerLambda( | SNSEvent | SESEvent | DynamoDBStreamEvent, - context: Context, - callback: (err: Error) => void, ) { const path = Object.keys(OPERATION_API.paths || {})?.[0]; const method = Object.keys(OPERATION_API.paths?.[path] || {})[0]; @@ -138,8 +134,6 @@ async function handleForAWSConsumerLambda( endTime: time(), recordsLength: event.Records.length, }); - - callback(null as unknown as Error); } catch (err) { const castedErr = YError.cast(err as Error); @@ -157,7 +151,7 @@ async function handleForAWSConsumerLambda( recordsLength: event.Records.length, }); - callback(err as Error); + throw castedErr; } } diff --git a/packages/whook-aws-lambda/src/wrappers/awsCronLambda.ts b/packages/whook-aws-lambda/src/wrappers/awsCronLambda.ts index ce341bcc..ed8db304 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsCronLambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsCronLambda.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { autoService } from 'knifecycle'; import { noop } from '@whook/whook'; import { printStackTrace, YError } from 'yerror'; @@ -12,7 +11,7 @@ import type { } from '@whook/whook'; import type { LogService, TimeService } from 'common-services'; import type { OpenAPIV3_1 } from 'openapi-types'; -import type { ScheduledEvent, Context } from 'aws-lambda'; +import type { ScheduledEvent } from 'aws-lambda'; import type { JsonObject } from 'type-fest'; import type { AppEnvVars } from 'application-services'; @@ -61,7 +60,7 @@ async function initWrapHandlerForCronLambda({ const wrappedHandler = handleForAWSCronLambda.bind( null, { ENV, OPERATION_API, apm, time, log }, - handler as any, + handler as WhookHandler, ); return wrappedHandler as unknown as S; @@ -80,8 +79,6 @@ async function handleForAWSCronLambda( }: Required, handler: WhookHandler, LambdaCronOutput>, event: ScheduledEvent & { body: T }, - context: Context, - callback: (err: Error) => void, ) { const path = Object.keys(OPERATION_API.paths || {})[0]; const method = Object.keys(OPERATION_API.paths?.[path] || {})[0]; @@ -109,7 +106,6 @@ async function handleForAWSCronLambda( startTime, endTime: time(), }); - callback(null as unknown as Error); } catch (err) { const castedErr = YError.cast(err as Error); @@ -126,7 +122,7 @@ async function handleForAWSCronLambda( endTime: time(), }); - callback(err as Error); + throw castedErr; } } diff --git a/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts b/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts index 00235932..fdc7f7f7 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts @@ -52,7 +52,6 @@ import { import type { APIGatewayProxyEvent, APIGatewayProxyResult, - Context, } from 'aws-lambda'; import type { WhookErrorHandler } from '@whook/http-router'; import type { AppEnvVars } from 'application-services'; @@ -262,8 +261,6 @@ async function handleForAWSHTTPLambda( }, handler: WhookHandler, event: APIGatewayProxyEvent, - context: Context, - callback: (err: Error, result?: APIGatewayProxyResult) => void, ) { const startTime = time(); const bufferLimit = bytes.parse(BUFFER_LIMIT); @@ -506,7 +503,8 @@ async function handleForAWSHTTPLambda( {}, ), }); - callback(null as unknown as Error, awsResponse); + + return awsResponse; } async function awsRequestEventToRequest( diff --git a/packages/whook-aws-lambda/src/wrappers/awsKafkaConsumerLambda.ts b/packages/whook-aws-lambda/src/wrappers/awsKafkaConsumerLambda.ts index 56cf14e5..55b6f2e3 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsKafkaConsumerLambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsKafkaConsumerLambda.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { autoService } from 'knifecycle'; import { noop } from '@whook/whook'; import { printStackTrace, YError } from 'yerror'; @@ -12,7 +11,7 @@ import type { } from '@whook/whook'; import type { TimeService, LogService } from 'common-services'; import type { OpenAPIV3_1 } from 'openapi-types'; -import type { MSKEvent, Context } from 'aws-lambda'; +import type { MSKEvent } from 'aws-lambda'; import type { AppEnvVars } from 'application-services'; export type LambdaKafkaConsumerInput = { body: MSKEvent['records'] }; @@ -61,7 +60,10 @@ async function initWrapHandlerForKafkaLambda({ const wrappedHandler = handleForAWSKafkaConsumerLambda.bind( null, { ENV, OPERATION_API, apm, time, log }, - handler as any, + handler as WhookHandler< + LambdaKafkaConsumerInput, + LambdaKafkaConsumerOutput + >, ); return wrappedHandler as unknown as S; @@ -80,8 +82,6 @@ async function handleForAWSKafkaConsumerLambda( }: Required, handler: WhookHandler, event: MSKEvent, - context: Context, - callback: (err: Error) => void, ) { const path = Object.keys(OPERATION_API.paths || {})[0]; const method = Object.keys(OPERATION_API.paths?.[path] || {})[0]; @@ -113,8 +113,6 @@ async function handleForAWSKafkaConsumerLambda( 0, ), }); - - callback(null as unknown as Error); } catch (err) { const castedErr = YError.cast(err as Error); @@ -135,7 +133,7 @@ async function handleForAWSKafkaConsumerLambda( ), }); - callback(err as Error); + throw castedErr; } } diff --git a/packages/whook-aws-lambda/src/wrappers/awsLogSubscriberLambda.ts b/packages/whook-aws-lambda/src/wrappers/awsLogSubscriberLambda.ts index 3794745b..5c8075d6 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsLogSubscriberLambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsLogSubscriberLambda.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import zlib from 'zlib'; import { autoService } from 'knifecycle'; import { noop } from '@whook/whook'; @@ -17,7 +16,6 @@ import type { JsonValue } from 'type-fest'; import type { CloudWatchLogsEvent, CloudWatchLogsDecodedData, - Context, } from 'aws-lambda'; import type { AppEnvVars } from 'application-services'; @@ -94,8 +92,6 @@ async function handleForAWSLogSubscriberLambda< }: Required, handler: S, event: CloudWatchLogsEvent, - context: Context, - callback: (err: Error) => void, ) { const path = Object.keys(OPERATION_API.paths || {})[0]; const method = Object.keys(OPERATION_API.paths?.[path] || {})[0]; @@ -124,8 +120,6 @@ async function handleForAWSLogSubscriberLambda< endTime: time(), recordsLength: parameters.body.logEvents.length, }); - - callback(null as unknown as Error); } catch (err) { const castedErr = YError.cast(err as Error); @@ -143,7 +137,7 @@ async function handleForAWSLogSubscriberLambda< recordsLength: parameters.body.logEvents.length, }); - callback(err as Error); + throw castedErr; } } diff --git a/packages/whook-aws-lambda/src/wrappers/awsS3Lambda.ts b/packages/whook-aws-lambda/src/wrappers/awsS3Lambda.ts index 56531928..21afc456 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsS3Lambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsS3Lambda.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { autoService } from 'knifecycle'; import { noop } from '@whook/whook'; import { printStackTrace, YError } from 'yerror'; @@ -12,7 +11,7 @@ import type { } from '@whook/whook'; import type { LogService, TimeService } from 'common-services'; import type { OpenAPIV3_1 } from 'openapi-types'; -import type { S3Event, Context } from 'aws-lambda'; +import type { S3Event } from 'aws-lambda'; import type { AppEnvVars } from 'application-services'; export type LambdaS3Input = { body: S3Event['Records'] }; @@ -76,8 +75,6 @@ async function handleForAWSS3Lambda( }: Required, handler: WhookHandler, event: S3Event, - context: Context, - callback: (err: Error) => void, ) { const path = Object.keys(OPERATION_API.paths || {})[0]; const method = Object.keys(OPERATION_API.paths?.[path] || {})[0]; @@ -105,7 +102,6 @@ async function handleForAWSS3Lambda( endTime: time(), recordsLength: event.Records.length, }); - callback(null as unknown as Error); } catch (err) { const castedErr = YError.cast(err as Error); @@ -123,7 +119,7 @@ async function handleForAWSS3Lambda( recordsLength: event.Records.length, }); - callback(err as Error); + throw castedErr; } } diff --git a/packages/whook-aws-lambda/src/wrappers/awsTransformerLambda.ts b/packages/whook-aws-lambda/src/wrappers/awsTransformerLambda.ts index 7e77e16e..5e3d3cce 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsTransformerLambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsTransformerLambda.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { autoService } from 'knifecycle'; import { noop } from '@whook/whook'; import { printStackTrace, YError } from 'yerror'; @@ -17,7 +16,6 @@ import type { FirehoseTransformationEventRecord, FirehoseTransformationResultRecord, FirehoseTransformationResult, - Context, } from 'aws-lambda'; import type { AppEnvVars } from 'application-services'; @@ -116,9 +114,7 @@ async function handleForAWSTransformerLambda( }: Required, handler: WhookHandler, event: FirehoseTransformationEvent, - context: Context, - callback: (err: Error | null, result?: FirehoseTransformationResult) => void, -) { +): Promise { const path = Object.keys(OPERATION_API.paths || {})[0]; const method = Object.keys(OPERATION_API.paths?.[path] || {})[0]; const OPERATION: WhookOperation = { @@ -146,7 +142,7 @@ async function handleForAWSTransformerLambda( recordsLength: event.records.length, }); - callback(null, { records: response.body.map(encodeRecord) }); + return { records: response.body.map(encodeRecord) }; } catch (err) { const castedErr = YError.cast(err as Error); @@ -164,7 +160,7 @@ async function handleForAWSTransformerLambda( recordsLength: event.records.length, }); - callback(err as Error); + throw castedErr; } }