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
184 changes: 183 additions & 1 deletion packages/core/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
Handler as LambdaHandler
Handler as AWSLambdaHandler
} from 'aws-lambda'

type ModifyReturnType<T, NewReturn> = T extends (
Copy link
Contributor Author

@cjbt cjbt Sep 13, 2023

Choose a reason for hiding this comment

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

I think I landed on an implementation. This gives it the ability to still depend on Handler from aws while it returns the new synced type

...args: infer A
) => infer R
? (...args: A) => R | NewReturn
: never;

// extended Handler type from aws-lambda
type LambdaHandler<TEvent = any, TResult = any> = ModifyReturnType<AWSLambdaHandler<TEvent, 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)

// synced 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)
Loading