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.41k stars 2.12k forks source link

Lack of the type of ‘total’ property in GraphQLResult if using searchXxx query #13766

Open HikaruTakakura opened 3 weeks ago

HikaruTakakura commented 3 weeks ago

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

GraphQL API

Amplify Version

v6

Amplify Categories

api

Backend

Amplify CLI

Environment information

``` # Put output below this line System: OS: macOS 14.4 CPU: (8) arm64 Apple M2 Memory: 62.75 MB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 20.6.1 - ~/.anyenv/envs/nodenv/versions/20.6.1/bin/node npm: 9.8.1 - ~/.anyenv/envs/nodenv/versions/20.6.1/bin/npm bun: 1.0.29 - ~/.bun/bin/bun Browsers: Chrome: 128.0.6613.86 Safari: 17.4 npmPackages: @ampproject/toolbox-optimizer: undefined () @aws-amplify/adapter-nextjs: ^1.0.30 => 1.2.10 @aws-amplify/adapter-nextjs/api: undefined () @aws-amplify/adapter-nextjs/data: undefined () @babel/core: undefined () @babel/runtime: 7.22.5 @edge-runtime/cookies: 4.1.0 @edge-runtime/ponyfill: 2.4.2 @edge-runtime/primitives: 4.1.0 @hapi/accept: undefined () @mswjs/interceptors: undefined () @napi-rs/triples: undefined () @next/font: undefined () @next/react-dev-overlay: undefined () @next/third-parties: ^14.1.4 => 14.1.4 @opentelemetry/api: undefined () @remixicon/react: ^4.2.0 => 4.2.0 @stripe/react-stripe-js: ^2.6.2 => 2.7.0 @stripe/stripe-js: ^3.2.0 => 3.3.0 @types/node: ^20 => 20.12.7 @types/react: ^18 => 18.2.75 @types/react-burger-menu: ^2.8.7 => 2.8.7 @types/react-dom: ^18 => 18.2.24 @types/react-modal: ^3.16.3 => 3.16.3 @vercel/nft: undefined () @vercel/og: 0.6.2 acorn: undefined () amphtml-validator: undefined () amplify: ^0.0.11 => 0.0.11 anser: undefined () arg: undefined () assert: undefined () async-retry: undefined () async-sema: undefined () autoprefixer: ^10.0.1 => 10.4.19 aws-amplify: ^6.0.30 => 6.4.3 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 () aws-sdk: ^2.1655.0 => 2.1655.0 axios: ^1.6.8 => 1.6.8 babel-packages: undefined () browser-image-compression: ^2.0.2 => 2.0.2 browserify-zlib: undefined () browserslist: undefined () buffer: undefined () bytes: undefined () ci-info: undefined () cli-select: undefined () client-only: 0.0.1 comment-json: undefined () compression: undefined () conf: undefined () constants-browserify: undefined () content-disposition: undefined () content-type: undefined () cookie: undefined () cross-spawn: undefined () crypto-browserify: undefined () css.escape: undefined () data-uri-to-buffer: undefined () date-fns: ^3.6.0 => 3.6.0 date-fns-tz: ^3.1.3 => 3.1.3 dayjs: ^1.11.10 => 1.11.10 debug: undefined () devalue: undefined () domain-browser: undefined () edge-runtime: undefined () eslint: ^8 => 8.57.0 eslint-config-next: 14.1.3 => 14.1.3 events: undefined () find-cache-dir: undefined () find-up: undefined () fresh: undefined () get-orientation: undefined () glob: undefined () gzip-size: undefined () http-proxy: undefined () http-proxy-agent: undefined () https-browserify: undefined () https-proxy-agent: undefined () icss-utils: undefined () ignore-loader: undefined () image-size: undefined () is-animated: undefined () is-docker: undefined () is-wsl: undefined () jest-worker: undefined () jotai: ^2.7.0 => 2.8.0 jotai-devtools: ^0.10.0 => 0.10.0 json5: undefined () jsonwebtoken: undefined () loader-runner: undefined () loader-utils: undefined () lodash.curry: undefined () lru-cache: undefined () micromatch: undefined () mini-css-extract-plugin: undefined () nanoid: undefined () native-url: undefined () neo-async: undefined () next: 14.1.3 => 14.1.3 node-fetch: undefined () node-html-parser: undefined () nookies: ^2.5.2 => 2.5.2 ora: undefined () os-browserify: undefined () p-limit: undefined () path-browserify: undefined () platform: undefined () postcss: ^8 => 8.4.38 (8.4.31) postcss-flexbugs-fixes: undefined () postcss-modules-extract-imports: undefined () postcss-modules-local-by-default: undefined () postcss-modules-scope: undefined () postcss-modules-values: undefined () postcss-preset-env: undefined () postcss-safe-parser: undefined () postcss-scss: undefined () postcss-value-parser: undefined () prettier: ^3.2.5 => 3.2.5 process: undefined () punycode: undefined () querystring-es3: undefined () raw-body: undefined () react: ^18 => 18.2.0 react-builtin: undefined () react-burger-menu: ^3.0.9 => 3.0.9 react-cropper: ^2.3.3 => 2.3.3 react-dom: ^18 => 18.2.0 (17.0.2) react-dom-builtin: undefined () react-dom-experimental-builtin: undefined () react-experimental-builtin: undefined () react-form-stepper: ^2.0.3 => 2.0.3 react-hook-form: ^7.51.0 => 7.51.2 react-hot-toast: ^2.4.1 => 2.4.1 react-is: 18.2.0 react-modal: ^3.16.1 => 3.16.1 react-refresh: 0.12.0 react-server-dom-turbopack-builtin: undefined () react-server-dom-turbopack-experimental-builtin: undefined () react-server-dom-webpack-builtin: undefined () react-server-dom-webpack-experimental-builtin: undefined () react-spring-bottom-sheet: ^3.4.1 => 3.4.1 regenerator-runtime: 0.13.4 sass: ^1.71.1 => 1.74.1 sass-loader: undefined () scheduler-builtin: undefined () scheduler-experimental-builtin: undefined () schema-utils: undefined () semver: undefined () send: undefined () server-only: 0.0.1 setimmediate: undefined () shell-quote: undefined () shortid: ^2.2.16 => 2.2.16 source-map: undefined () stacktrace-parser: undefined () stream-browserify: undefined () stream-http: undefined () string-hash: undefined () string_decoder: undefined () strip-ansi: undefined () superstruct: undefined () swiper: ^11.0.7 => 11.1.1 swr: ^2.2.5 => 2.2.5 tailwindcss: ^3.3.0 => 3.4.3 tar: undefined () terser: undefined () text-table: undefined () timers-browserify: undefined () tty-browserify: undefined () typescript: ^5 => 5.4.5 ua-parser-js: undefined () unistore: undefined () util: undefined () vm-browserify: undefined () watchpack: undefined () web-vitals: undefined () webpack: undefined () webpack-sources: undefined () ws: undefined () zod: undefined () npmGlobalPackages: @squoosh/cli: 0.7.3 corepack: 0.19.0 npm: 9.8.1 react-devtools: 5.0.0 ```

Describe the bug

Property total does not exist on type 'PagedList<{ __typename: "Cat"; ...'.

const response = await client.graphql({
  query: searchCats,
  variables: {
    // ...
  } satisfies SearchCatsQueryVariables,
})

response.data.searchCats?.total // Property total does not exist on type 'PagedList<{ __typename: "Cat"; ...'.

The reason is that WithListsFixed<T> removes properties that exist in T but are not included in PagedList. https://github.com/aws-amplify/amplify-js/blob/ced891c2e4f6b0f1fdeaf44ab80cae9d585b6d15/packages/api-graphql/src/types/index.ts#L105

In fact, the error disappeared after making the following changes

type WithListsFixed<T> =
  T extends PagedList<infer IT, infer NAME>
    ? PagedList<Exclude<IT, null | undefined>, NAME> &
        Exclude<T, keyof PagedList<any, any>>
    : T extends Record<string, unknown>
      ? {
          [K in keyof T]: WithListsFixed<T[K]>
        }
      : T

Expected behavior

The total property exists, so there should not be an error

Reproduction steps

  1. Generate search query (searchCats) using @searchable.
  2. Run query like const response = await client.graphql({ query: searchCats })
  3. Try to get total property on response.data.searchCats

Code Snippet

No response

Log output

No response

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 @HikaruTakakura can you share your schema?

HikaruTakakura commented 3 weeks ago

@chrisbonifacio

Thank you for your prompt reply. This is my schema.

type Cat
  @model
  @searchable
  @auth(
    rules: [
      { allow: public, operations: [read, create, update, delete] }
    ]
  ) {
  id: ID!
  name: String
  about: String
  deletedAt: AWSDateTime
  updatedAt: AWSDateTime!
  createdAt: AWSDateTime!
}
chrisbonifacio commented 3 weeks ago

@HikaruTakakura hmm, I don't think that we support a total field from openSearch out of the box. You may have to edit the VTL template for the resolver to set a track_total_hits parameter to true as mentioned in this feature request:

https://github.com/aws-amplify/amplify-category-api/issues/1079

HikaruTakakura commented 3 weeks ago

@chrisbonifacio Thanks for the information. However, the issue I am raising is not the implementation of the total field but its type.

Here is the query and its type generated by the schema shown above.

// queries.ts
export const searchCats = /* GraphQL */ `query SearchCats(
  $filter: SearchableCatFilterInput
  $sort: SearchableCatSortInput
  $limit: Int
  $nextToken: String
  $from: Int
) {
  searchCats(
    filter: $filter
    sort: $sort
    limit: $limit
    nextToken: $nextToken
    from: $from
  ) {
    items {
      // ...
    }
    nextToken
    total
    __typename
  }
}
` as GeneratedQuery<
  APITypes.SearchCatsQueryVariables,
  APITypes.SearchCatsQuery
>;
// API.ts
export type SearchCatsQuery = {
  searchCats?:  {
    __typename: "SearchableCatConnection",
    items:  Array< {
      __typename: "Cat",
      // ...
    } | null >,
    nextToken?: string | null,
    total?: number | null,
  } | null,
};

It is clear that the response to the query should contain a total field, and in fact it does return a value for that field, but it gives a type error. As you can see in the screenshot below, it does not appear in the predictive text.

スクリーンショット 2024-08-31 17 45 29

So now we use @ts-ignore to suppress that error.

chrisbonifacio commented 2 weeks ago

Hi @HikaruTakakura 👋 thanks for raising this issue!

I was able to get total from the return type by using the SearchCatsQuery type from API.ts like this:

import { SearchCatsQuery } from "@/src/API";
import { searchCats } from "@/src/graphql/queries";
import { GraphQLQuery } from "aws-amplify/api";

// ...
const searchCatsQuery = async () => {
  const result = await client.graphql<GraphQLQuery<SearchCatsQuery>>({
    query: searchCats,
  })

CleanShot 2024-09-06 at 12 32 17@2x

HikaruTakakura commented 2 weeks ago

Hi @chrisbonifacio thank you very much. In this case, however, the presence of the total field should be inferred without type annotation. And that can be done by the modification of WithListsFixed I first showed. What are the problems with this modification approach?

chrisbonifacio commented 3 days ago

@HikaruTakakura re-opened this issue and will discuss with the team!

In the meantime, please submit a Pull Request with the proposed fix for the team to review