Skip to content

Generating Narrower Types #1538

Closed
Closed
@harrysolovay

Description

@harrysolovay

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.

Feedback would be greatly appreciated!

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestNew feature or enhancement. May require GitHub community feedback.p2This is a standard priority issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions