Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): allow sync handlers #1102

Merged
merged 11 commits into from
Sep 17, 2023
2 changes: 1 addition & 1 deletion packages/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type MiddyInputHandler<
context: TContext,
callback: LambdaCallback<TResult>
) => // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
void | Promise<TResult>
void | Promise<TResult> | TResult
type MiddyInputPromiseHandler<
TEvent,
TResult,
Expand Down
186 changes: 184 additions & 2 deletions packages/core/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import middy from '.'
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
Handler as LambdaHandler
Callback,
Context
} from 'aws-lambda'

// extended Handler type from aws-lambda
// to include synced TResult
type LambdaHandler<TEvent = any, TResult = any> = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an ideal solution is to somehow extend the return type of the original Handler type from aws. Looking to see if that's even possible.

event: TEvent,
context: Context,
callback: Callback<TResult>,
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
) => void | Promise<TResult> | TResult

const lambdaHandler: LambdaHandler<APIGatewayProxyEvent, APIGatewayProxyResult> = async (event) => {
return {
statusCode: 200,
Expand Down Expand Up @@ -196,3 +205,176 @@ expectType<middy.MiddyfiedHandler<unknown>>(streamifiedResponseHandler)

streamifiedResponseHandler.handler(lambdaHandler)
streamifiedResponseHandler.use(middlewareObj)

// non async handler
const syncedLambdaHandler: LambdaHandler<APIGatewayProxyEvent, APIGatewayProxyResult> = (event) => {
return {
statusCode: 200,
body: `Hello from ${event.path}`
}
}

// initialize
let syncedHandler = middy(syncedLambdaHandler)
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
expectType<Handler>(syncedHandler)

// initialize with empty plugin
syncedHandler = middy(syncedLambdaHandler, {})
expectType<Handler>(syncedHandler)

// initialize with plugin with few hooks
syncedHandler = middy(syncedLambdaHandler, {
beforePrefetch () { console.log('beforePrefetch') }
})
expectType<Handler>(syncedHandler)

// initialize with plugin with all hooks
syncedHandler = middy(syncedLambdaHandler, {
beforePrefetch () { console.log('beforePrefetch') },
requestStart () { console.log('requestStart') },
beforeMiddleware (name: string) { console.log('beforeMiddleware', name) },
afterMiddleware (name: string) { console.log('afterMiddleware', name) },
beforeHandler () { console.log('beforeHandler') },
afterHandler () { console.log('afterHandler') },
async requestEnd () { console.log('requestEnd') }
})
expectType<Handler>(syncedHandler)

// invokes the handler to test that it is callable
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
async function invokSyncedHandler (): Promise<void | APIGatewayProxyResult> {
const sampleEvent: APIGatewayProxyEvent = {
resource: '/',
path: '/',
httpMethod: 'GET',
requestContext: {
resourcePath: '/',
httpMethod: 'GET',
path: '/Prod/',
accountId: 'x',
apiId: 'y',
authorizer: {},
protocol: 'p',
identity: {
accessKey: '',
accountId: '',
apiKey: '',
apiKeyId: '',
caller: '',
clientCert: null,
cognitoAuthenticationProvider: '',
cognitoAuthenticationType: '',
cognitoIdentityId: '',
cognitoIdentityPoolId: '',
principalOrgId: '',
sourceIp: '',
user: '',
userAgent: '',
userArn: ''
},
stage: '',
requestId: '',
requestTimeEpoch: 12345567,
resourceId: ''
},
headers: {
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'accept-encoding': 'gzip, deflate, br',
Host: '70ixmpl4fl.execute-api.us-east-2.amazonaws.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36',
'X-Amzn-Trace-Id': 'Root=1-5e66d96f-7491f09xmpl79d18acf3d050'
},
multiValueHeaders: {
accept: [
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
],
'accept-encoding': [
'gzip, deflate, br'
]
},
queryStringParameters: null,
multiValueQueryStringParameters: null,
pathParameters: null,
stageVariables: null,
body: null,
isBase64Encoded: false
}
const sampleContext: Context = {
callbackWaitsForEmptyEventLoop: true,
functionName: '',
functionVersion: '',
invokedFunctionArn: '',
memoryLimitInMB: '234',
awsRequestId: '',
logGroupName: '',
logStreamName: '',
getRemainingTimeInMillis: (): number => 1,
done: () => { },
fail: (_) => { },
succeed: () => { }
}
return await syncedHandler(sampleEvent, sampleContext, () => {})
cjbt marked this conversation as resolved.
Show resolved Hide resolved
}
invokSyncedHandler().catch(console.error)

// use with 1 middleware
syncedHandler = syncedHandler.use(middlewareObj)
expectType<Handler>(syncedHandler)

// use with array of middlewares
syncedHandler = syncedHandler.use([middlewareObj])
expectType<Handler>(syncedHandler)

// before
syncedHandler = syncedHandler.before((request: Request) => { console.log('Before', request) })
expectType<Handler>(syncedHandler)

// after
syncedHandler = syncedHandler.after((request: Request) => { console.log('After', request) })
expectType<Handler>(syncedHandler)

// error
syncedHandler = syncedHandler.onError((request: Request) => { console.log('OnError', request) })
expectType<Handler>(syncedHandler)

interface MutableContext extends Context {
name: string
}

function syncedMutableContextDependantHandler (event: APIGatewayProxyEvent, context: MutableContext): APIGatewayProxyResult {
return {
statusCode: 200,
body: `Hello from ${context.name}`
}
}

let customSyncedCtxHandler = middy<APIGatewayProxyEvent, APIGatewayProxyResult, Error, MutableContext>(syncedMutableContextDependantHandler)
expectType<MutableContextHandler>(customSyncedCtxHandler)

// @ts-expect-error
customSyncedCtxHandler = middy<APIGatewayProxyEvent, APIGatewayProxyResult, Error, Context>(syncedMutableContextDependantHandler)

const mutableSyncedContextMiddleware = {
before: (request: MutableContextRequest) => {
request.context.name = 'Foo'
}
}

customSyncedCtxHandler = customSyncedCtxHandler.use(mutableSyncedContextMiddleware)
expectType<MutableContextHandler>(customSyncedCtxHandler)

const syncedTypeErrorMiddleware = {
before: (request: MutableContextRequest) => {
// @ts-expect-error
request.context.test = 'Bar'
}
}

customSyncedCtxHandler = customSyncedCtxHandler.use(syncedTypeErrorMiddleware)
expectType<MutableContextHandler>(customSyncedCtxHandler)

const syncedStreamifiedResponseHandler = middy({ streamifyResponse: true })
expectType<middy.MiddyfiedHandler<unknown>>(syncedStreamifiedResponseHandler)

syncedStreamifiedResponseHandler.handler(syncedLambdaHandler)
syncedStreamifiedResponseHandler.use(middlewareObj)