From 17e9857f8dea9aaa88b82204d5da9620f4a4f1cb Mon Sep 17 00:00:00 2001 From: Simon Buchan Date: Wed, 7 Feb 2018 21:47:58 +1300 Subject: [PATCH 1/4] [aws-lambda] Add Handler and Callback types for each trigger. No tests added for the new types yet, I'll get on that tomorrow. Motivation --- The connection between the trigger event and the result types can be confusing sometimes - for example `CloudFormationCustomResourceResponse` is *not* returned from the lambda, but instead should be sent to the *Request `ResponseUrl`. This PR tries to help by providing pre-baked `Handler` types for each of the triggers (that have types already). There's also a few niggles with naming and the handler signature that have been bothering me for a while. Changes --- This PR: - Adds defaulted generic parameters to the existing `Handler` and `Callback` types. This increases the minimum TS version to 2.3. - Makes the `callback` parameter to `Handler`s "required" - this probably originally was meant to represent the Node 0.10 runtime, which did not pass a callback parameter, but that is no longer selectable when creating or editing a Lambda and makes the normal, recommended usage harder. In case anyone is confused, this doesn't break any code: it is required to be provided when being called, your handlers don't need to declare it. I'm not removing the legacy node 0.10 runtime methods `context.done()` etc., since they still work. - In the spirit of #21874, make the default result type for `Handler`, `Callback` `any`, since the only requirement is that it be `JSON.stringify()`able, and there are things like `.toJSON()`, etc. so there is no meaningful type restriction here. - Revert the definition changes of #22588, which changed the `Handler` result types to `void | Promise`, which is misleading: Lambda will not accept or wait for a promise result (it by default waits for the node event loop to empty). This is not a breaking change for code that does return promises, since a `void` result type is compatible with any result type, even with strict function types. (Which is why the tests still pass.) - Adds `FooHandler` and `FooCallback` types for all `FooEvent`s and `FooResult`s, where possible - sometimes these don't match up. - Renamed, with aliases to the old name, various types to align them: in particular `ProxyResult` -> `APIGatewayProxyResult`. - `CloudFrontResult` (not the same type as `CloudFrontResponse`!) was missing for some reason. --- types/aws-lambda/index.d.ts | 76 ++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/types/aws-lambda/index.d.ts b/types/aws-lambda/index.d.ts index 09070fb13c..a87c6da946 100644 --- a/types/aws-lambda/index.d.ts +++ b/types/aws-lambda/index.d.ts @@ -14,8 +14,9 @@ // Markus Tacker // Palmi Valgeirsson // Danilo Raisi +// Simon Buchan // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.3 // API Gateway "event" request context export interface APIGatewayEventRequestContext { @@ -191,9 +192,10 @@ export interface S3EventRecord { }; } -export interface S3CreateEvent { +export interface S3Event { Records: S3EventRecord[]; } +export type S3CreateEvent = S3Event; // old name /** * Cognito User Pool event @@ -414,7 +416,7 @@ export interface ClientContextEnv { locale: string; } -export interface ProxyResult { +export interface APIGatewayProxyResult { statusCode: number; headers?: { [header: string]: boolean | number | string; @@ -422,16 +424,18 @@ export interface ProxyResult { body: string; isBase64Encoded?: boolean; } +export type ProxyResult = APIGatewayProxyResult; // Old name /** * API Gateway CustomAuthorizer AuthResponse. * http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html#api-gateway-custom-authorizer-output */ -export interface AuthResponse { +export interface CustomAuthorizerResult { principalId: string; policyDocument: PolicyDocument; context?: AuthResponseContext; } +export type AuthResponse = CustomAuthorizerResult; /** * API Gateway CustomAuthorizer AuthResponse.PolicyDocument. @@ -509,6 +513,15 @@ export interface CloudFrontRequestEvent { }>; } +// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-generating-http-responses.html#lambda-generating-http-responses-object +export interface CloudFrontResult { + status: string; + statusDescription?: string; + headers?: CloudFrontHeaders; + bodyEncoding?: 'text' | 'base64'; + body?: string; +} + /** * AWS Lambda handler function. * http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html @@ -517,9 +530,7 @@ export interface CloudFrontRequestEvent { * @param context – runtime information of the Lambda function that is executing. * @param callback – optional callback to return information to the caller, otherwise return value is null. */ -export type Handler = (event: any, context: Context, callback?: Callback) => Promise | void; -export type ProxyHandler = (event: APIGatewayEvent, context: Context, callback?: ProxyCallback) => Promise | void; -export type CustomAuthorizerHandler = (event: CustomAuthorizerEvent, context: Context, callback?: CustomAuthorizerCallback) => Promise | void; +export type Handler = (event: TEvent, context: Context, callback: Callback) => void; /** * Optional callback parameter. @@ -528,8 +539,53 @@ export type CustomAuthorizerHandler = (event: CustomAuthorizerEvent, context: Co * @param error – an optional parameter that you can use to provide results of the failed Lambda function execution. * @param result – an optional parameter that you can use to provide the result of a successful function execution. The result provided must be JSON.stringify compatible. */ -export type Callback = (error?: Error | null, result?: object | boolean | number | string) => void; -export type ProxyCallback = (error?: Error | null, result?: ProxyResult) => void; -export type CustomAuthorizerCallback = (error?: Error | null, result?: AuthResponse) => void; +export type Callback = (error?: Error | null, result?: TResult) => void; + +// Begin defining Handler and Callback types for each API trigger type. +// Ordered by https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html +// though that list is incomplete. + +export type S3Handler = Handler; + +export type DynamoDBHandler = Handler; + +export type SNSHandler = Handler; + +// No SESHandler: SES event source is delivered as SNS notifications +// https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-ses + +export type CognitoUserPoolTriggerHandler = Handler; +// TODO: Different event triggers/response pairs? + +// TODO: CognitoSync + +export type CloudFormationCustomResourceHandler = Handler; + +// TODO: CloudWatchEvents + +export type CloudWatchLogsHandler = Handler; + +// TODO: CodeCommit + +export type ScheduledEventHandler = Handler; + +// TODO: AWS Config + +// TODO: Alexa + +export type APIGatewayProxyHandler = Handler; +export type ProxyHandler = APIGatewayProxyHandler; // Old name +export type APIGatewayProxyCallback = Callback; +export type ProxyCallback = APIGatewayProxyCallback; // Old name + +// TODO: IoT + +export type CloudFrontRequestHandler = Handler; +export type CloudFrontResponseHandler = Handler; + +// TODO: Kinesis (should be very close to DynamoDB stream?) + +export type CustomAuthorizerHandler = Handler; +export type CustomAuthorizerCallback = Callback; export as namespace AWSLambda; From 7bfcc391dff2ca8a5f092f3a5fc7236002af0658 Mon Sep 17 00:00:00 2001 From: Simon Buchan Date: Thu, 8 Feb 2018 10:19:46 +1300 Subject: [PATCH 2/4] Update apex.js to fix CI. --- types/apex.js/apex.js-tests.ts | 2 +- types/apex.js/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/types/apex.js/apex.js-tests.ts b/types/apex.js/apex.js-tests.ts index 0595ef43ea..f5d96e2c54 100644 --- a/types/apex.js/apex.js-tests.ts +++ b/types/apex.js/apex.js-tests.ts @@ -1,4 +1,4 @@ -import * as λ from "apex.js"; +import λ = require("apex.js"); const handler = λ((event, context) => { console.log("Event: " + JSON.stringify(event)); diff --git a/types/apex.js/index.d.ts b/types/apex.js/index.d.ts index 0998ce794c..20e05d2a5e 100644 --- a/types/apex.js/index.d.ts +++ b/types/apex.js/index.d.ts @@ -2,7 +2,7 @@ // Project: https://github.com/apex/node-apex // Definitions by: Yoriki Yamaguchi // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.3 /// From e43b20962169519c5e01099b1b3af8db93eb75e6 Mon Sep 17 00:00:00 2001 From: Simon Buchan Date: Thu, 8 Feb 2018 11:59:17 +1300 Subject: [PATCH 3/4] Fix tests for strictNullChecks: true --- types/aws-lambda/aws-lambda-tests.ts | 172 ++++++++++++++------------- types/aws-lambda/tsconfig.json | 4 +- 2 files changed, 91 insertions(+), 85 deletions(-) diff --git a/types/aws-lambda/aws-lambda-tests.ts b/types/aws-lambda/aws-lambda-tests.ts index 6d0d83b210..bbe2ea7a81 100644 --- a/types/aws-lambda/aws-lambda-tests.ts +++ b/types/aws-lambda/aws-lambda-tests.ts @@ -1,28 +1,34 @@ -let str: string; -let date: Date; -let anyObj: any; -let num: number; -let error: Error; -let b: boolean; -let apiGwEvtReqCtx: AWSLambda.APIGatewayEventRequestContext; -let apiGwEvt: AWSLambda.APIGatewayEvent; -let customAuthorizerEvt: AWSLambda.CustomAuthorizerEvent; -let clientCtx: AWSLambda.ClientContext; -let clientContextEnv: AWSLambda.ClientContextEnv; -let clientContextClient: AWSLambda.ClientContextClient; -let context: AWSLambda.Context; -let identity: AWSLambda.CognitoIdentity; -let proxyResult: AWSLambda.ProxyResult; -let authResponse: AWSLambda.AuthResponse; -let policyDocument: AWSLambda.PolicyDocument; -let statement: AWSLambda.Statement; -let authResponseContext: AWSLambda.AuthResponseContext; -let snsEvt: AWSLambda.SNSEvent; -let snsEvtRecs: AWSLambda.SNSEventRecord[]; -let snsEvtRec: AWSLambda.SNSEventRecord; -let snsMsg: AWSLambda.SNSMessage; -let snsMsgAttr: AWSLambda.SNSMessageAttribute; -let snsMsgAttrs: AWSLambda.SNSMessageAttributes; +declare let str: string; +declare let strOrNull: string | null; +declare let strOrUndefined: string | undefined; +declare let date: Date; +declare let anyObj: any; +declare let num: number; +declare let error: Error; +declare let bool: boolean; +declare let boolOrUndefined: boolean | undefined; +declare let apiGwEvtReqCtx: AWSLambda.APIGatewayEventRequestContext; +declare let apiGwEvt: AWSLambda.APIGatewayEvent; +declare let customAuthorizerEvt: AWSLambda.CustomAuthorizerEvent; +declare let clientCtx: AWSLambda.ClientContext; +declare let clientCtxOrUndefined: AWSLambda.ClientContext | undefined; +declare let clientContextEnv: AWSLambda.ClientContextEnv; +declare let clientContextClient: AWSLambda.ClientContextClient; +declare let context: AWSLambda.Context; +declare let identity: AWSLambda.CognitoIdentity; +declare let identityOrUndefined: AWSLambda.CognitoIdentity | undefined; +declare let proxyResult: AWSLambda.ProxyResult; +declare let authResponse: AWSLambda.AuthResponse; +declare let policyDocument: AWSLambda.PolicyDocument; +declare let statement: AWSLambda.Statement; +declare let authResponseContext: AWSLambda.AuthResponseContext; +declare let authResponseContextOpt: AWSLambda.AuthResponseContext | null | undefined; +declare let snsEvt: AWSLambda.SNSEvent; +declare let snsEvtRecs: AWSLambda.SNSEventRecord[]; +declare let snsEvtRec: AWSLambda.SNSEventRecord; +declare let snsMsg: AWSLambda.SNSMessage; +declare let snsMsgAttr: AWSLambda.SNSMessageAttribute; +declare let snsMsgAttrs: AWSLambda.SNSMessageAttributes; const S3EvtRec: AWSLambda.S3EventRecord = { eventVersion: '2.0', eventSource: 'aws:s3', @@ -72,44 +78,44 @@ declare const scheduledEvent: AWSLambda.ScheduledEvent; /* API Gateway Event request context */ str = apiGwEvtReqCtx.accountId; str = apiGwEvtReqCtx.apiId; -authResponseContext = apiGwEvtReqCtx.authorizer; +authResponseContextOpt = apiGwEvtReqCtx.authorizer; str = apiGwEvtReqCtx.httpMethod; -str = apiGwEvtReqCtx.identity.accessKey; -str = apiGwEvtReqCtx.identity.accountId; -str = apiGwEvtReqCtx.identity.apiKey; -str = apiGwEvtReqCtx.identity.caller; -str = apiGwEvtReqCtx.identity.cognitoAuthenticationProvider; -str = apiGwEvtReqCtx.identity.cognitoAuthenticationType; -str = apiGwEvtReqCtx.identity.cognitoIdentityId; -str = apiGwEvtReqCtx.identity.cognitoIdentityPoolId; +strOrNull = apiGwEvtReqCtx.identity.accessKey; +strOrNull = apiGwEvtReqCtx.identity.accountId; +strOrNull = apiGwEvtReqCtx.identity.apiKey; +strOrNull = apiGwEvtReqCtx.identity.caller; +strOrNull = apiGwEvtReqCtx.identity.cognitoAuthenticationProvider; +strOrNull = apiGwEvtReqCtx.identity.cognitoAuthenticationType; +strOrNull = apiGwEvtReqCtx.identity.cognitoIdentityId; +strOrNull = apiGwEvtReqCtx.identity.cognitoIdentityPoolId; str = apiGwEvtReqCtx.identity.sourceIp; -str = apiGwEvtReqCtx.identity.user; -str = apiGwEvtReqCtx.identity.userAgent; -str = apiGwEvtReqCtx.identity.userArn; +strOrNull = apiGwEvtReqCtx.identity.user; +strOrNull = apiGwEvtReqCtx.identity.userAgent; +strOrNull = apiGwEvtReqCtx.identity.userArn; str = apiGwEvtReqCtx.stage; str = apiGwEvtReqCtx.requestId; str = apiGwEvtReqCtx.resourceId; str = apiGwEvtReqCtx.resourcePath; /* API Gateway Event */ -str = apiGwEvt.body; +strOrNull = apiGwEvt.body; str = apiGwEvt.headers["example"]; str = apiGwEvt.httpMethod; -b = apiGwEvt.isBase64Encoded; +bool = apiGwEvt.isBase64Encoded; str = apiGwEvt.path; -str = apiGwEvt.pathParameters["example"]; -str = apiGwEvt.queryStringParameters["example"]; -str = apiGwEvt.stageVariables["example"]; +str = apiGwEvt.pathParameters!["example"]; +str = apiGwEvt.queryStringParameters!["example"]; +str = apiGwEvt.stageVariables!["example"]; apiGwEvtReqCtx = apiGwEvt.requestContext; str = apiGwEvt.resource; /* API Gateway CustomAuthorizer Event */ str = customAuthorizerEvt.type; str = customAuthorizerEvt.methodArn; -str = customAuthorizerEvt.authorizationToken; -str = apiGwEvt.pathParameters["example"]; -str = apiGwEvt.queryStringParameters["example"]; -str = apiGwEvt.stageVariables["example"]; +strOrUndefined = customAuthorizerEvt.authorizationToken; +str = apiGwEvt.pathParameters!["example"]; +str = apiGwEvt.queryStringParameters!["example"]; +str = apiGwEvt.stageVariables!["example"]; apiGwEvtReqCtx = apiGwEvt.requestContext; /* DynamoDB Stream Event */ @@ -234,17 +240,17 @@ str = snsMsgAttr.Value; /* Lambda Proxy Result */ num = proxyResult.statusCode; -proxyResult.headers["example"] = str; -proxyResult.headers["example"] = b; -proxyResult.headers["example"] = num; -b = proxyResult.isBase64Encoded; +proxyResult.headers!["example"] = str; +proxyResult.headers!["example"] = bool; +proxyResult.headers!["example"] = num; +boolOrUndefined = proxyResult.isBase64Encoded; str = proxyResult.body; /* API Gateway CustomAuthorizer AuthResponse */ authResponseContext = { stringKey: str, numberKey: num, - booleanKey: b + booleanKey: bool }; statement = { @@ -300,36 +306,36 @@ cognitoUserPoolEvent.triggerSource === "TokenGeneration_AuthenticateDevice"; cognitoUserPoolEvent.triggerSource === "TokenGeneration_RefreshTokens"; str = cognitoUserPoolEvent.region; str = cognitoUserPoolEvent.userPoolId; -str = cognitoUserPoolEvent.userName; +strOrUndefined = cognitoUserPoolEvent.userName; str = cognitoUserPoolEvent.callerContext.awsSdkVersion; str = cognitoUserPoolEvent.callerContext.clientId; str = cognitoUserPoolEvent.request.userAttributes["email"]; -str = cognitoUserPoolEvent.request.validationData["k1"]; -str = cognitoUserPoolEvent.request.codeParameter; -str = cognitoUserPoolEvent.request.usernameParameter; -b = cognitoUserPoolEvent.request.newDeviceUsed; -cognitoUserPoolEvent.request.session[0].challengeName === "CUSTOM_CHALLENGE"; -cognitoUserPoolEvent.request.session[0].challengeName === "PASSWORD_VERIFIER"; -cognitoUserPoolEvent.request.session[0].challengeName === "SMS_MFA"; -cognitoUserPoolEvent.request.session[0].challengeName === "DEVICE_SRP_AUTH"; -cognitoUserPoolEvent.request.session[0].challengeName === "DEVICE_PASSWORD_VERIFIER"; -cognitoUserPoolEvent.request.session[0].challengeName === "ADMIN_NO_SRP_AUTH"; -b = cognitoUserPoolEvent.request.session[0].challengeResult; -str = cognitoUserPoolEvent.request.session[0].challengeMetaData; -str = cognitoUserPoolEvent.request.challengeName; -str = cognitoUserPoolEvent.request.privateChallengeParameters["answer"]; -str = cognitoUserPoolEvent.request.challengeAnswer["answer"]; -b = cognitoUserPoolEvent.response.answerCorrect; -str = cognitoUserPoolEvent.response.smsMessage; -str = cognitoUserPoolEvent.response.emailMessage; -str = cognitoUserPoolEvent.response.emailSubject; -str = cognitoUserPoolEvent.response.challengeName; -b = cognitoUserPoolEvent.response.issueTokens; -b = cognitoUserPoolEvent.response.failAuthentication; -str = cognitoUserPoolEvent.response.publicChallengeParameters["captchaUrl"]; -str = cognitoUserPoolEvent.response.privateChallengeParameters["answer"]; -str = cognitoUserPoolEvent.response.challengeMetaData; -b = cognitoUserPoolEvent.response.answerCorrect; +str = cognitoUserPoolEvent.request.validationData!["k1"]; +strOrUndefined = cognitoUserPoolEvent.request.codeParameter; +strOrUndefined = cognitoUserPoolEvent.request.usernameParameter; +boolOrUndefined = cognitoUserPoolEvent.request.newDeviceUsed; +cognitoUserPoolEvent.request.session![0].challengeName === "CUSTOM_CHALLENGE"; +cognitoUserPoolEvent.request.session![0].challengeName === "PASSWORD_VERIFIER"; +cognitoUserPoolEvent.request.session![0].challengeName === "SMS_MFA"; +cognitoUserPoolEvent.request.session![0].challengeName === "DEVICE_SRP_AUTH"; +cognitoUserPoolEvent.request.session![0].challengeName === "DEVICE_PASSWORD_VERIFIER"; +cognitoUserPoolEvent.request.session![0].challengeName === "ADMIN_NO_SRP_AUTH"; +bool = cognitoUserPoolEvent.request.session![0].challengeResult; +strOrUndefined = cognitoUserPoolEvent.request.session![0].challengeMetaData; +strOrUndefined = cognitoUserPoolEvent.request.challengeName; +str = cognitoUserPoolEvent.request.privateChallengeParameters!["answer"]; +str = cognitoUserPoolEvent.request.challengeAnswer!["answer"]; +boolOrUndefined = cognitoUserPoolEvent.response.answerCorrect; +strOrUndefined = cognitoUserPoolEvent.response.smsMessage; +strOrUndefined = cognitoUserPoolEvent.response.emailMessage; +strOrUndefined = cognitoUserPoolEvent.response.emailSubject; +strOrUndefined = cognitoUserPoolEvent.response.challengeName; +boolOrUndefined = cognitoUserPoolEvent.response.issueTokens; +boolOrUndefined = cognitoUserPoolEvent.response.failAuthentication; +str = cognitoUserPoolEvent.response.publicChallengeParameters!["captchaUrl"]; +str = cognitoUserPoolEvent.response.privateChallengeParameters!["answer"]; +strOrUndefined = cognitoUserPoolEvent.response.challengeMetaData; +boolOrUndefined = cognitoUserPoolEvent.response.answerCorrect; // CloudFormation Custom Resource switch (cloudformationCustomResourceEvent.RequestType) { @@ -353,7 +359,7 @@ switch (cloudformationCustomResourceEvent.RequestType) { anyObj = cloudformationCustomResourceResponse.Data; str = cloudformationCustomResourceResponse.LogicalResourceId; str = cloudformationCustomResourceResponse.PhysicalResourceId; -str = cloudformationCustomResourceResponse.Reason; +strOrUndefined = cloudformationCustomResourceResponse.Reason; str = cloudformationCustomResourceResponse.RequestId; str = cloudformationCustomResourceResponse.StackId; str = cloudformationCustomResourceResponse.Status; @@ -368,7 +374,7 @@ str = scheduledEvent.source; str = scheduledEvent.time; /* Context */ -b = context.callbackWaitsForEmptyEventLoop; +bool = context.callbackWaitsForEmptyEventLoop; str = context.functionName; str = context.functionVersion; str = context.invokedFunctionArn; @@ -376,8 +382,8 @@ num = context.memoryLimitInMB; str = context.awsRequestId; str = context.logGroupName; str = context.logStreamName; -identity = context.identity; -clientCtx = context.clientContext; +identityOrUndefined = context.identity; +clientCtxOrUndefined = context.clientContext; /* CognitoIdentity */ str = identity.cognitoIdentityId; @@ -420,7 +426,7 @@ function callback(cb: AWSLambda.Callback) { cb(null); cb(error); cb(null, anyObj); - cb(null, b); + cb(null, bool); cb(null, str); cb(null, num); } diff --git a/types/aws-lambda/tsconfig.json b/types/aws-lambda/tsconfig.json index 37e2690d7e..ffb0849a71 100644 --- a/types/aws-lambda/tsconfig.json +++ b/types/aws-lambda/tsconfig.json @@ -6,7 +6,7 @@ ], "noImplicitAny": true, "noImplicitThis": true, - "strictNullChecks": false, + "strictNullChecks": true, "strictFunctionTypes": true, "baseUrl": "../", "typeRoots": [ @@ -21,4 +21,4 @@ "index.d.ts", "aws-lambda-tests.ts" ] -} \ No newline at end of file +} From 50dafbd8e340f232392f5d7b88d163a046ebfdd5 Mon Sep 17 00:00:00 2001 From: Simon Buchan Date: Thu, 8 Feb 2018 12:39:00 +1300 Subject: [PATCH 4/4] Add tests, fix some missed type name normalizations. --- types/aws-lambda/aws-lambda-tests.ts | 68 +++++++++++++++++++++++++++- types/aws-lambda/index.d.ts | 47 +++++++++++-------- 2 files changed, 95 insertions(+), 20 deletions(-) diff --git a/types/aws-lambda/aws-lambda-tests.ts b/types/aws-lambda/aws-lambda-tests.ts index bbe2ea7a81..bd29160912 100644 --- a/types/aws-lambda/aws-lambda-tests.ts +++ b/types/aws-lambda/aws-lambda-tests.ts @@ -584,8 +584,72 @@ context.fail(str); /* Handler */ let handler: AWSLambda.Handler = (event: any, context: AWSLambda.Context, cb: AWSLambda.Callback) => { }; + +// async methods return Promise, test assignability let asyncHandler: AWSLambda.Handler = async (event: any, context: AWSLambda.Context, cb: AWSLambda.Callback) => { }; + +let inferredHandler: AWSLambda.S3Handler = (event, context, cb) => { + // $ExpectType S3Event + event; + str = event.Records[0].eventName; + // $ExpectType Context + context; + str = context.functionName; + // $ExpectType Callback + cb; + cb(); + cb(null); + cb(new Error()); + // $ExpectError + cb(null, { }); +}; + +// Test using default Callback type still works. +let defaultCallbackHandler: AWSLambda.APIGatewayProxyHandler = (event: AWSLambda.APIGatewayEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => { }; + +// Specific types +let s3Handler: AWSLambda.S3Handler = (event: AWSLambda.S3Event, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; +// Test old name +let s3CreateHandler: AWSLambda.S3Handler = (event: AWSLambda.S3CreateEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; +s3Handler = s3CreateHandler; + +let dynamoDBStreamHandler: AWSLambda.DynamoDBStreamHandler = (event: AWSLambda.DynamoDBStreamEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; + +let snsHandler: AWSLambda.SNSHandler = (event: AWSLambda.SNSEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; + +let cognitoUserPoolHandler: AWSLambda.CognitoUserPoolTriggerHandler = (event: AWSLambda.CognitoUserPoolEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; + +let cloudFormationCustomResourceHandler: AWSLambda.CloudFormationCustomResourceHandler = + (event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; + +let cloudWatchLogsHandler: AWSLambda.CloudWatchLogsHandler = (event: AWSLambda.CloudWatchLogsEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; + +let scheduledHandler: AWSLambda.ScheduledHandler = (event: AWSLambda.ScheduledEvent, context: AWSLambda.Context, cb: AWSLambda.Callback) => {}; + +let apiGtwProxyHandler: AWSLambda.APIGatewayProxyHandler = (event: AWSLambda.APIGatewayProxyEvent, context: AWSLambda.Context, cb: AWSLambda.APIGatewayProxyCallback) => { }; +// Test old names let proxyHandler: AWSLambda.ProxyHandler = (event: AWSLambda.APIGatewayEvent, context: AWSLambda.Context, cb: AWSLambda.ProxyCallback) => { }; -let asyncProxyHandler: AWSLambda.ProxyHandler = async (event: AWSLambda.APIGatewayEvent, context: AWSLambda.Context, cb: AWSLambda.ProxyCallback) => { }; +apiGtwProxyHandler = proxyHandler; + +let cloudFrontRequestHandler: AWSLambda.CloudFrontRequestHandler = (event: AWSLambda.CloudFrontRequestEvent, context: AWSLambda.Context, cb: AWSLambda.CloudFrontRequestCallback) => { }; + +let cloudFrontResponseHandler: AWSLambda.CloudFrontResponseHandler = (event: AWSLambda.CloudFrontResponseEvent, context: AWSLambda.Context, cb: AWSLambda.CloudFrontResponseCallback) => { }; + let customAuthorizerHandler: AWSLambda.CustomAuthorizerHandler = (event: AWSLambda.CustomAuthorizerEvent, context: AWSLambda.Context, cb: AWSLambda.CustomAuthorizerCallback) => { }; -let asyncCustomAuthorizerHandler: AWSLambda.CustomAuthorizerHandler = async (event: AWSLambda.CustomAuthorizerEvent, context: AWSLambda.Context, cb: AWSLambda.CustomAuthorizerCallback) => { }; + +interface CustomEvent { eventString: string; eventBool: boolean; } +interface CustomResult { resultString: string; resultBool?: boolean; } +type CustomCallback = AWSLambda.Callback; +let customHandler: AWSLambda.Handler = (event, context, cb) => { + // $ExpectType CustomEvent + event; + str = event.eventString; + bool = event.eventBool; + // $ExpectType Context + context; + // $ExpectType Callback + cb; + cb(null, { resultString: str, resultBool: bool }); + // $ExpectError + cb(null, { resultString: bool }); +}; diff --git a/types/aws-lambda/index.d.ts b/types/aws-lambda/index.d.ts index a87c6da946..03910b108c 100644 --- a/types/aws-lambda/index.d.ts +++ b/types/aws-lambda/index.d.ts @@ -46,7 +46,7 @@ export interface APIGatewayEventRequestContext { } // API Gateway "event" -export interface APIGatewayEvent { +export interface APIGatewayProxyEvent { body: string | null; headers: { [name: string]: string }; httpMethod: string; @@ -58,6 +58,7 @@ export interface APIGatewayEvent { requestContext: APIGatewayEventRequestContext; resource: string; } +export type APIGatewayEvent = APIGatewayProxyEvent; // Old name // API Gateway CustomAuthorizer "event" export interface CustomAuthorizerEvent { @@ -201,7 +202,7 @@ export type S3CreateEvent = S3Event; // old name * Cognito User Pool event * http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html */ -export interface CognitoUserPoolEvent { +export interface CognitoUserPoolTriggerEvent { version: number; triggerSource: | "PreSignUp_SignUp" @@ -261,6 +262,7 @@ export interface CognitoUserPoolEvent { answerCorrect?: boolean; }; } +export type CognitoUserPoolEvent = CognitoUserPoolTriggerEvent; /** * CloudFormation Custom Resource event and response @@ -496,6 +498,15 @@ export interface CloudFrontEvent { }; } +// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-generating-http-responses.html#lambda-generating-http-responses-object +export interface CloudFrontResultResponse { + status: string; + statusDescription?: string; + headers?: CloudFrontHeaders; + bodyEncoding?: 'text' | 'base64'; + body?: string; +} + export interface CloudFrontResponseEvent { Records: Array<{ cf: CloudFrontEvent & { @@ -505,6 +516,8 @@ export interface CloudFrontResponseEvent { }>; } +export type CloudFrontRequestResult = undefined | null | CloudFrontResultResponse; + export interface CloudFrontRequestEvent { Records: Array<{ cf: CloudFrontEvent & { @@ -513,14 +526,7 @@ export interface CloudFrontRequestEvent { }>; } -// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-generating-http-responses.html#lambda-generating-http-responses-object -export interface CloudFrontResult { - status: string; - statusDescription?: string; - headers?: CloudFrontHeaders; - bodyEncoding?: 'text' | 'base64'; - body?: string; -} +export type CloudFrontResponseResult = undefined | null | CloudFrontResultResponse; /** * AWS Lambda handler function. @@ -547,15 +553,17 @@ export type Callback = (error?: Error | null, result?: TResult) = export type S3Handler = Handler; -export type DynamoDBHandler = Handler; +export type DynamoDBStreamHandler = Handler; export type SNSHandler = Handler; // No SESHandler: SES event source is delivered as SNS notifications // https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-ses -export type CognitoUserPoolTriggerHandler = Handler; -// TODO: Different event triggers/response pairs? +// Result type is weird: docs and samples say to return the mutated event, but it only requires an object +// with a "response" field, the type of which is specific to the event.triggerType. Leave as any for now. +export type CognitoUserPoolTriggerHandler = Handler; +// TODO: Different event/handler types for each event trigger so we can type the result? // TODO: CognitoSync @@ -567,21 +575,24 @@ export type CloudWatchLogsHandler = Handler; // TODO: CodeCommit -export type ScheduledEventHandler = Handler; +export type ScheduledHandler = Handler; // TODO: AWS Config // TODO: Alexa -export type APIGatewayProxyHandler = Handler; -export type ProxyHandler = APIGatewayProxyHandler; // Old name +export type APIGatewayProxyHandler = Handler; export type APIGatewayProxyCallback = Callback; +export type ProxyHandler = APIGatewayProxyHandler; // Old name export type ProxyCallback = APIGatewayProxyCallback; // Old name // TODO: IoT -export type CloudFrontRequestHandler = Handler; -export type CloudFrontResponseHandler = Handler; +export type CloudFrontRequestHandler = Handler; +export type CloudFrontRequestCallback = Callback; + +export type CloudFrontResponseHandler = Handler; +export type CloudFrontResponseCallback = Callback; // TODO: Kinesis (should be very close to DynamoDB stream?)