serverless-operations / serverless-step-functions

AWS Step Functions plugin for Serverless Framework ⚡️
Other
1.03k stars 203 forks source link

Add TypeScript types #585

Closed zirkelc closed 1 year ago

zirkelc commented 1 year ago

This PR adds TypeScript types as discussed in #370

First, you need to use the official TypeScript @serverless/typescript package. for your serverless.ts file.

TypeScript merges the types from this package with the base types from @serverless/typescript with either of these two options:

I can't guarantee that the types are fully complete and correct, but I think there are a good starting point.

If you rather prefer to have the types provided as a separate @types/serverless-step-functions package, I'd be willing to submit a PR to the DefinetlyTypes package.

ebisbe commented 1 year ago

Also when I try to export the state machine to a single file it fails with:

Cannot load "serverless.ts": Initialization error: TSError: ⨯ Unable to compile TypeScript:
serverless.ts:650:7 - error TS2322: Type '{ events: { schedule: { rate: string; enabled: boolean; }; }[]; dependsOn: string[]; definition: { StartAt: string; States: { "Add empty LastEvaluatedKey": { Type: string; Next: string; Result: { LastEvaluatedKey: string; }; ResultPath: string; }; ... 4 more ...; "Add new LastEvaluatedKey": { ...; }; }; }; }' is not assignable to type '{ id?: string; name?: string; events?: any; dependsOn?: any; definition: Definition; tracingConfig?: TracingConfig; }'.
  The types of 'definition.States' are incompatible between these types.
    Type '{ "Add empty LastEvaluatedKey": { Type: string; Next: string; Result: { LastEvaluatedKey: string; }; ResultPath: string; }; Scan: { Type: string; Next: string; Parameters: { TableName: { Ref: string; }; FilterExpression: string; ExpressionAttributeNames: { ...; }; ExpressionAttributeValues: { ...; }; "ExclusiveStart...' is not assignable to type 'States'.
      Property '"Add empty LastEvaluatedKey"' is incompatible with index signature.
        Type '{ Type: string; Next: string; Result: { LastEvaluatedKey: string; }; ResultPath: string; }' is not assignable to type 'Choice | Fail | Map | Task | Parallel | Pass | Wait | Succeed'.
          Type '{ Type: string; Next: string; Result: { LastEvaluatedKey: string; }; ResultPath: string; }' is not assignable to type 'Pass'.
            Types of property 'Type' are incompatible.
              Type 'string' is not assignable to type '"Pass"'.

650       addCredits,
          ~~~~~~~~~~

  node_modules/serverless-step-functions/lib/index.d.ts:13:3
     13   [stateMachine: string]: {
          ~~~~~~~~~~~~~~~~~~~~~~~~~
     14     id?: string;
        ~~~~~~~~~~~~~~~~
    ... 
     19     tracingConfig?: TracingConfig;
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     20   };
        ~~~~
    The expected type comes from this index signature.
zirkelc commented 1 year ago

Also when I try to export the state machine to a single file it fails with:

What do you mean with exporting it to a single file? The types use module augmentation to merge the types into the official AWS type:

https://github.com/serverless-operations/serverless-step-functions/blob/c464dcdc0e0255cd93170486c7fd907b9893c020/lib/index.d.ts#L3-L10

If you want to use the types as stand-alone, we should probably export the stepFunctions type separately.

Is that what you mean?

ebisbe commented 1 year ago

Also when I try to export the state machine to a single file it fails with:

What do you mean with exporting it to a single file? The types use module augmentation to merge the types into the official AWS type:

https://github.com/serverless-operations/serverless-step-functions/blob/c464dcdc0e0255cd93170486c7fd907b9893c020/lib/index.d.ts#L3-L10

If you want to use the types as stand-alone, we should probably export the stepFunctions type separately.

Is that what you mean?

Nope. What I wanted to have in a different file is the stateMachine implementation. Not the types. So I can have a file for each StateMachine and split things so I don't have a huge serverless.ts file.

I've also found that we are missing type?: 'STANDARD' | 'EXPRESS'; and loggingConfig?: any; ( https://github.com/serverless-operations/serverless-step-functions/issues/281 )

zirkelc commented 1 year ago

Also when I try to export the state machine to a single file it fails with:

What do you mean with exporting it to a single file? The types use module augmentation to merge the types into the official AWS type: https://github.com/serverless-operations/serverless-step-functions/blob/c464dcdc0e0255cd93170486c7fd907b9893c020/lib/index.d.ts#L3-L10

If you want to use the types as stand-alone, we should probably export the stepFunctions type separately. Is that what you mean?

Nope. What I wanted to have in a different file is the stateMachine implementation. Not the types. So I can have a file for each StateMachine and split things so I don't have a huge serverless.ts file.

Yes, I think the issue wouldn't appear if you could type your stateMachine like this:

export const stateMachine1: StateMachine = { 
  // ...
};

I assume the issue is related to type widening by TypeScript. So the last error message as an example:

Types of property 'Type' are incompatible. Type 'string' is not assignable to type '"Pass"'.

The state machine expects type to be a string literal of Pass or Task etc. But when you define your state machine outside of the serverless type, TypeScript widens the type from literal Pass to string. That causes the type error I guess.

ebisbe commented 1 year ago

I assume the issue is related to type widening by TypeScript. So the last error message as an example:

Types of property 'Type' are incompatible. Type 'string' is not assignable to type '"Pass"'.

The state machine expects type to be a string literal of Pass or Task etc. But when you define your state machine outside of the serverless type, TypeScript widens the type from literal Pass to string. That causes the type error I guess.

Ok, I didn't know the term or what was really happening but that's exactly the issue. Then what we could do is export the single type StateMachine

ebisbe commented 1 year ago

Everything is working from my side. Not sure if we are missing anything.

Edit: A few updates.

zirkelc commented 1 year ago

@horike37 could you please take a look if you have the chance. If you prefer not to include TS types in this package, we can move this PR to the DefinitelyTyped repo.

horike37 commented 1 year ago

@zirkelc Thank you for this PR! Adding the type system should be really important. But right now we can't regularly maintain this repo, so it would be great if you can move this PR to the DefinitelyTyped repo.

zirkelc commented 1 year ago

@horike37 thanks for you reply! Alright, I'll submit a PR to DefinitelyTyped in the next few days.

zirkelc commented 1 year ago

@ebisbe I created a new PR in the DefinetlyTyped repo: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/66693

I didn't try it yet, but I think to get the types merged from this package, we have to use module augmentation in our serverless.ts file directly:

import type { AWS } from '@serverless/typescript';
import type { StepFunctions } from 'serverless-step-functions';

declare module '@serverless/typescript' {
   interface AWS {
     stepFunctions?: StepFunctions;
  }
}
ebisbe commented 1 year ago

we have to use module augmentation

I don't know what's that it. I will try to help you on the other PR.

zirkelc commented 1 year ago

It means add types from package serverless-step-functions into the base types of @serverless/typescript. However, I figured out that type packages can install other type packages. So we get it to work without add this part at the top:

import type { AWS } from '@serverless/typescript';
import type { StepFunctions } from 'serverless-step-functions';

declare module '@serverless/typescript' {
   interface AWS {
     stepFunctions?: StepFunctions;
  }
}

We have to wait for the approval on the DT repo.

ebisbe commented 1 year ago

Ok. What I don't like on this PR is that we have been doing it manually... It would be perfect if we could extract the full state machine definition and convert it to types so it can be automated. Here it's for CF resources https://github.com/awboost/cfntypes ( only sadly )

zirkelc commented 1 year ago

Yes, I totally agree! Unfortunately, there is no official JSON schema that we can rely on. The official spec doesn't publish any specification: https://states-language.net/spec.html

There is this package that provides validation for ASL: https://github.com/ChristopheBougere/asl-validator

It has some JSON schemas for the actual states: https://github.com/ChristopheBougere/asl-validator/tree/main/src/schemas It could be possible to generate TS types from these files, but I didn't check if it's complete.

It might be possible to generate the types with this package https://www.npmjs.com/package/json-schema-to-typescript

ebisbe commented 1 year ago

@zirkelc this package has a validation option. Let me see what is using under the hood.

EDIT: It's using "asl-validator": "^3.8.0", the package you mentioned.