aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.42k stars 2.12k forks source link

Cannot type dto model - TS2589: Type instantiation is excessively deep and possibly infinite. #13758

Closed WojciechKasprzyk closed 3 weeks ago

WojciechKasprzyk commented 3 weeks ago

Before opening, please confirm:

JavaScript Framework

Angular

Amplify APIs

GraphQL API

Amplify Version

v6

Amplify Categories

api

Backend

Amplify CLI

Environment information

``` System: OS: macOS 14.6.1 CPU: (10) arm64 Apple M1 Pro Memory: 73.05 MB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 22.2.0 - /usr/local/bin/node npm: 10.7.0 - /usr/local/bin/npm bun: 1.0.0 - ~/.bun/bin/bun Browsers: Chrome: 127.0.6533.122 Safari: 17.6 Safari Technology Preview: 18.0 npmPackages: @angular-devkit/build-angular: ^18.2.1 => 18.2.1 @angular/animations: ^18.2.1 => 18.2.1 @angular/cli: ^18.2.1 => 18.2.1 @angular/common: ^18.2.1 => 18.2.1 @angular/compiler: ^18.2.1 => 18.2.1 @angular/compiler-cli: ^18.2.1 => 18.2.1 @angular/core: ^18.2.1 => 18.2.1 @angular/forms: ^18.2.1 => 18.2.1 @angular/platform-browser: ^18.2.1 => 18.2.1 @angular/platform-browser-dynamic: ^18.2.1 => 18.2.1 @angular/router: ^18.2.1 => 18.2.1 @aws-amplify/backend: ^1.1.1 => 1.1.1 @aws-amplify/backend-cli: ^1.2.5 => 1.2.5 @aws-amplify/ui-angular: ^5.0.19 => 5.0.19 @ngrx/effects: ^18.0.2 => 18.0.2 @ngrx/entity: ^18.0.2 => 18.0.2 @ngrx/eslint-plugin: ^18.0.2 => 18.0.2 @ngrx/operators: ^18.0.2 => 18.0.2 (18.0.1) @ngrx/schematics: ^18.0.2 => 18.0.2 @ngrx/store: ^18.0.2 => 18.0.2 @ngrx/store-devtools: ^18.0.2 => 18.0.2 @types/jasmine: ~5.1.4 => 5.1.4 aws-amplify: ^6.5.2 => 6.5.2 aws-amplify/adapter-core: undefined () aws-amplify/analytics: undefined () aws-amplify/analytics/kinesis: undefined () aws-amplify/analytics/kinesis-firehose: undefined () aws-amplify/analytics/personalize: undefined () aws-amplify/analytics/pinpoint: undefined () aws-amplify/api: undefined () aws-amplify/api/server: undefined () aws-amplify/auth: undefined () aws-amplify/auth/cognito: undefined () aws-amplify/auth/cognito/server: undefined () aws-amplify/auth/enable-oauth-listener: undefined () aws-amplify/auth/server: undefined () aws-amplify/data: undefined () aws-amplify/data/server: undefined () aws-amplify/datastore: undefined () aws-amplify/in-app-messaging: undefined () aws-amplify/in-app-messaging/pinpoint: undefined () aws-amplify/push-notifications: undefined () aws-amplify/push-notifications/pinpoint: undefined () aws-amplify/storage: undefined () aws-amplify/storage/s3: undefined () aws-amplify/storage/s3/server: undefined () aws-amplify/storage/server: undefined () aws-amplify/utils: undefined () jasmine-core: ~5.2.0 => 5.2.0 (4.6.1) karma: ~6.4.4 => 6.4.4 karma-chrome-launcher: ~3.2.0 => 3.2.0 karma-coverage: ~2.2.1 => 2.2.1 karma-coverage-coffee-example: 1.0.0 karma-jasmine: ~5.1.0 => 5.1.0 karma-jasmine-html-reporter: ~2.1.0 => 2.1.0 rxjs: ~7.8.1 => 7.8.1 rxjs/ajax: undefined () rxjs/fetch: undefined () rxjs/operators: undefined () rxjs/testing: undefined () rxjs/webSocket: undefined () tslib: ^2.7.0 => 2.7.0 (2.6.3, 2.0.3, 2.4.1, 2.2.0, 2.3.1, 1.14.1) typescript: ~5.5.4 => 5.5.4 (4.4.4, 4.9.5) zone.js: ~0.14.10 => 0.14.10 npmGlobalPackages: @angular/cli: 16.1.6 @aws-amplify/cli: 12.12.4 @ionic/cli: 7.2.0 cordova-res: 0.15.4 cordova: 12.0.0 corepack: 0.28.1 jest: 29.7.0 n: 9.2.3 npm: 10.7.0 ts-node: 10.9.2 ```

Describe the bug

My project uses an external backend, which I fetch to retrieve a schema. I want to present the part of the schema:

const schema = a.schema({
  CmsPage: a
    .model({
      id: a.id().required(),
      gci: a.id().required(),
      channelID: a.id().required(),
      name: a.string().required(),
      createdBy: a.id(),
      slug: a.string(),
      title: a.string(),
      content: a.string(),
      metaDescription: a.string(),
      metaKeywords: a.string(),
      isActive: a.boolean(),
      isDefault: a.boolean(),
      isPublic: a.boolean()
    })
    .identifier(['id', 'gci', 'channelID'])
    .secondaryIndexes((index) => [
      index("gci").queryField("listCmsPagesByGci"),
      index("channelID").queryField("listCmsPagesByChannel").sortKeys(["gci"]),
    ])
    .authorization((allow) => [allow.publicApiKey()]),

  CmsMenu: a
    .model({
      id: a.id().required(),
      gci: a.id().required(),
      channelID: a.id().required(),
      symbol: a.string().required(),
      items: a.json(),
      hidOnSitemap: a.boolean(),
      createdBy: a.id(),
      languageKey: a.string(),
      description: a.string(),
      isActive: a.boolean(),
      isPublic: a.boolean()
    })
    .identifier(['id', 'gci', 'channelID'])
    .secondaryIndexes((index) => [
      index("gci").queryField("listCmsMenusByGci"),
      index("channelID").queryField("listCmsMenusByChannel").sortKeys(["gci"]),
    ])
    .authorization((allow) => [allow.publicApiKey()]),

})

Then I want to get the type of models and there are two problems:

  1. If there none of the model fields is a type of JSON I can get the page type
    
    import api from "@/api";

export type PageDto = Awaited<ReturnType>;

export type PageItemsDto = PageDto['data']; export type PageItemDto = PageItemsDto['0'];

But the way I get it is quite brutal. Is there any other way to get the schema model type?

2. If there **any** of the model fields is a type of **JSON** (like CmsMenu of my example)  I cannot do anything because TS thinks that it's possibly looped

``` typescript
import api from "@/api";

export type MenuDto = Awaited<ReturnType<typeof api.models.CmsMenu.list>>;  //ERROR: TS2589: Type instantiation is excessively deep and possibly infinite.

export type MenuItemsDto = MenuDto['data'];
export type MenuItemDto = MenuItemsDto['0'];
image

Expected behavior

I expect either:

  1. DTO Interface that I can import
  2. json() field type that does not break with error TS2589
  3. Any other way to type a model

Reproduction steps

npm install git submodule update --init --recursive npx ampx generate outputs --branch master --app-id {{backendId}}

Code Snippet

// Put your code below this line.

Log output

``` // Put your logs below this line ```

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

chrisbonifacio commented 3 weeks ago

Hi @WojciechKasprzyk we're having some trouble reproducing the error message in a similar manner.

I tried getting the type for export type PostData = Awaited<ReturnType<typeof client.models.Post.list>>; but did not get the same error. I also tried downgrading my TS version to 4.9.5 and 4.4.4 as they are mentioned in your environment info but ran into different errors like Awaited not being available.

Can you provide a minimal sample app that reproduces the error? It seems we are both are using the latest versions of @aws-amplify/backend and @aws-amplify/backend-cli so it's hard to tell what the difference is.

It might also be worth checking that your @aws-amplify/data-schema package is also up to date.

In any case, we also have type helpers that we expose on the Schema type. You should be able to use these instead.

https://docs.amplify.aws/javascript/build-a-backend/data/query-data/#typescript-type-helpers-for-amplify-data

chrisbonifacio commented 3 weeks ago

Update: We were able to reproduce the error but it seemed finnicky. If we got the type from Schema['CmsMenu']['type'] in the same file as export type CmsMenuData = Awaited<ReturnType<typeof client.models.CmsMenu.list>>; the error would go away. Not sure if it's an issue with VSCode or the TS server. But, are you able to use the Type helpers from Schema instead?

Is there a particular reason why you need the return type for a list query? An alternative might be to do this instead:

export type MenuDto = Schema['CmsMenu']['type'];

type MenuListReturn = {
  data: MenuDto[];
  errors?: object[];
}
WojciechKasprzyk commented 3 weeks ago

Hi @chrisbonifacio, Thank you for your response. The way using Schema works very well and I can get the type. I want this type to adapt DTO to the frontend data model in a separate helper file (adapter).