Description
Is your feature request related to a problem? Please describe.
The smithy-generated TypeScript types can be safer and more inference-producing. For example:
Cognito User Pool's IntiateAuth
command accounts for many auth flows (represented by the AuthFlowType
enum). Based on that supplied enum, we can discriminate between AuthParameters
to type-check flow-specific input fields. We can also return the correct flow-specific outputs (narrowed ChallengeName
, ChallengeParameters
, AuthenticationResult
(its omission), etc.).
For the RespondToAuthChallenge
command, here are some of the varying signatures we might get (currently untyped).
const smsMfa = await client.send(new RespondToAuthChallengeCommand({
AuthFlow: ChallengeTypeName.SMS_MFA,
...otherFields,
}));
// {
// ChallengeName: ChallengeTypeName.SMS_MFA;
// SMS_MFA_CODE: string;
// USERNAME: string;
// }
const passwordVerifier = await client.send(new RespondToAuthChallengeCommand({
AuthFlow: ChallengeTypeName.PASSWORD_VERIFIER,
...otherFields,
}))
// {
// ChallengeName: ChallengeTypeName.PASSWORD_VERIFIER;
// PASSWORD_CLAIM_SIGNATURE: string;
// PASSWORD_CLAIM_SECRET_BLOCK: string;
// TIMESTAMP: string;
// USERNAME: string;
// }
const newPasswordRequired = await client.send(new RespondToAuthChallengeCommand({
AuthFlow: ChallengeTypeName.NEW_PASSWORD_REQUIRED,
...otherFields,
}))
// {
// ChallengeName: ChallengeTypeName.NEW_PASSWORD_REQUIRED;
// NEW_PASSWORD: string;
// USERNAME: string;
// attributes: {Name: string; Value: string}[];
// }
Describe the solution you'd like
Let's discuss the approach to smithy-model-to-ts-type codegen. Ideally, we could provide more information, not present in the Smithy models. This info could enable inference of the correct return types based on inputs.
[EDIT]:
If we could specify the input-specific output types like this (for InitiateAuth
, in this example):
{
InitiateAuth: {
ifInput: [{
extends: {
AuthFlow: "AuthFlowType.USER_SRP_AUTH",
},
outputConstraints: {
AuthenticationResult: "undefined",
ChallengeName: "ChallengeNameType.PASSWORD_VERIFIER",
ChallengeParameters: {
SALT: "string",
SECRET_BLOCK: "string",
SRP_B: "string",
USERNAME: "string",
USER_ID_FOR_SRP: "string",
},
SESSION: "undefined",
},
},
}],
}
We could generate a generic output type such as this:
type InitiateAuthCommandOutput<I extends InitiateAuthInput> = I extends {
AuthFlow: AuthFlowType.USER_SRP_AUTH;
}
? {
AuthenticationResult: undefined;
ChallengeName: ChallengeNameType.PASSWORD_VERIFIER;
ChallengeParameters: {
SALT: string;
SECRET_BLOCK: string;
SRP_B: string;
USERNAME: string;
USER_ID_FOR_SRP: string;
};
SESSION: undefined;
}
: WideInitiateAuthOutput;
The InitiateAuth command can keep a generic reference to the constructor param, and pass the narrowed type into $Command
.
class InitiateAuthCommand<
I extends InitiateAuthCommandInput,
O extends InitiateAuthCommandOutput<I>
> extends $Command<
InitiateAuthCommandInput,
O,
CognitoIdentityProviderClientResolvedConfig
> {
readonly input: InitiateAuthCommandInput;
constructor(input: I);
resolveMiddleware(
clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
configuration: CognitoIdentityProviderClientResolvedConfig,
options?: __HttpHandlerOptions,
): Handler<InitiateAuthCommandInput, O>;
private serialize;
private deserialize;
}
The caveat is that the input params need to be narrowed (readonly), but it's still a huge DX improvement for TypeScript power users, and would enable us to safeguard––at the type-level––against all kinds of misuse.
Additional context
To avoid breaking backwards-compat, we could expose the narrowed types as a set of module augmentations, available in @aws-sdk/types/strict.ts
.
[EDIT]: moved semi-related (but probably for-now unproductive snippet to comment below)
In Summary
I believe stricter types are essential to customer success, as they serve both in protecting against misuse and ease of experience.