aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
81 stars 71 forks source link

(npx ampx generate graphql-client-code): types are not generated from the schema. #2578

Open naedx opened 1 month ago

naedx commented 1 month ago

Environment information

System:
  OS: macOS 14.5
  CPU: arm64 Apple
  Shell: /bin/zsh
Binaries:
  Node: 20.9.0 - /usr/local/bin/node
  Yarn: 1.22.22 - ~/.npm-global/bin/yarn
  npm: 10.8.0 - ~/.npm-global/bin/npm
  pnpm: 9.1.2 - ~/.npm-global/bin/pnpm
NPM Packages:
  @aws-amplify/backend: 1.0.2
  @aws-amplify/backend-cli: 1.0.3
  aws-amplify: 6.3.4
  aws-cdk: 2.143.0
  aws-cdk-lib: 2.143.0
  typescript: 5.4.5
AWS environment variables:
  AWS_STS_REGIONAL_ENDPOINTS = regional
  AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  AWS_SDK_LOAD_CONFIG = 1
No CDK environment variables

Description

Describe the bug

npx ampx generate graphql-client-code --format graphql-codegen --out lib/api --statement-target graphql

The command above generates mutations.graphql, subscriptions.graphql and queries.graphql. Each of these files lists the appropriate GraphQL operations. However only the operations are generated:

  1. the root types are not generated
type Mutation { } # <<< missing
type Query { } # <<< missing
type Subscription { } # <<< missing
  1. the user types from schema are not generated

    How can this bug be reproduced?

  2. Follow the quickstart guide to create a gen2 project

  3. run the command npx ampx generate graphql-client-code --format graphql-codegen --out lib/api --statement-target graphql

  4. observe that the user types and the subtypes are not generated.

naedx commented 1 month ago

npx @aws-amplify/cli codegen add --apiId XXXX --region XXXX

This command also produces the same result.

naedx commented 1 month ago

Workaround:

aws appsync get-introspection-schema --api-id XXXXX --format SDL --no-include-directives lib/api/amplify/schema.graphql

Using the command above outputs the schema as expected.

ykethan commented 1 month ago

Hey👋 thanks for raising this! I'm going to transfer this over to our API repository for better assistance 🙂

AnilMaktala commented 1 month ago

Hey @naedx, Could you please share the generated mutation.js file after running the command npx ampx generate graphql-client-code --format graphql-codegen --out lib/api --statement-target graphql?

naedx commented 1 month ago

Hi @AnilMaktala, using the schema defined in the QuickStart guide I get:

# this is an auto generated file. This will be overwritten

mutation CreateTodo(
  $condition: ModelTodoConditionInput
  $input: CreateTodoInput!
) {
  createTodo(condition: $condition, input: $input) {
    content
    createdAt
    id
    updatedAt
    __typename
  }
}

mutation DeleteTodo(
  $condition: ModelTodoConditionInput
  $input: DeleteTodoInput!
) {
  deleteTodo(condition: $condition, input: $input) {
    content
    createdAt
    id
    updatedAt
    __typename
  }
}

mutation UpdateTodo(
  $condition: ModelTodoConditionInput
  $input: UpdateTodoInput!
) {
  updateTodo(condition: $condition, input: $input) {
    content
    createdAt
    id
    updatedAt
    __typename
  }
}
```graphql # this is an auto generated file. This will be overwritten query GetTodo($id: ID!) { getTodo(id: $id) { content createdAt id updatedAt __typename } } query ListTodos( $filter: ModelTodoFilterInput $limit: Int $nextToken: String ) { listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) { items { content createdAt id updatedAt __typename } nextToken __typename } } ``` ```graphql # this is an auto generated file. This will be overwritten subscription OnCreateTodo($filter: ModelSubscriptionTodoFilterInput) { onCreateTodo(filter: $filter) { content createdAt id updatedAt __typename } } subscription OnDeleteTodo($filter: ModelSubscriptionTodoFilterInput) { onDeleteTodo(filter: $filter) { content createdAt id updatedAt __typename } } subscription OnUpdateTodo($filter: ModelSubscriptionTodoFilterInput) { onUpdateTodo(filter: $filter) { content createdAt id updatedAt __typename } } ```
darko-sameplace commented 4 weeks ago

Try this file to see if it is going to create a different model:

import { type ClientSchema, a, defineData } from '@aws-amplify/backend';

const schema = a.schema({ GameSession: a .model({ id: a.id(), level: a.integer(), durationInSeconds: a.integer(), }) .authorization(allow => [allow.owner()]) });

export type Schema = ClientSchema;

export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: "userPool", }, });

naedx commented 4 weeks ago

Hi @darko-sameplace . The environment doesn't deploy anything with the code that you've provided:


awstest2 % npx ampx sandbox --outputs-format dart --outputs-out-dir lib --outputs-version 0

  Amplify Sandbox

  Identifier:   XXXXXX
  Stack:        XXXXXX

  To specify a different sandbox identifier, use --identifier

amplify/data/resource.ts(20,22): error TS2314: Generic type 'ClientSchema' requires 1 type argument(s).
TypeScript validation check failed.
Resolution: Fix the syntax and type errors in your backend definition.

To fix it I've changed:

export type Schema = ClientSchema;

to

export type Schema = ClientSchema<typeof schema>;

This is the schema it generates and deploys to the cloud:

```graphql input CreateGameSessionInput { id: ID level: Int durationInSeconds: Int } input DeleteGameSessionInput { id: ID! } type GameSession @aws_iam @aws_cognito_user_pools { id: ID! level: Int durationInSeconds: Int createdAt: AWSDateTime! updatedAt: AWSDateTime! owner: String } enum ModelAttributeTypes { binary binarySet bool list map number numberSet string stringSet _null } input ModelBooleanInput { ne: Boolean eq: Boolean attributeExists: Boolean attributeType: ModelAttributeTypes } input ModelFloatInput { ne: Float eq: Float le: Float lt: Float ge: Float gt: Float between: [Float] attributeExists: Boolean attributeType: ModelAttributeTypes } input ModelGameSessionConditionInput { level: ModelIntInput durationInSeconds: ModelIntInput and: [ModelGameSessionConditionInput] or: [ModelGameSessionConditionInput] not: ModelGameSessionConditionInput createdAt: ModelStringInput updatedAt: ModelStringInput owner: ModelStringInput } type ModelGameSessionConnection @aws_iam @aws_cognito_user_pools { items: [GameSession]! nextToken: String } input ModelGameSessionFilterInput { id: ModelIDInput level: ModelIntInput durationInSeconds: ModelIntInput createdAt: ModelStringInput updatedAt: ModelStringInput and: [ModelGameSessionFilterInput] or: [ModelGameSessionFilterInput] not: ModelGameSessionFilterInput owner: ModelStringInput } input ModelIDInput { ne: ID eq: ID le: ID lt: ID ge: ID gt: ID contains: ID notContains: ID between: [ID] beginsWith: ID attributeExists: Boolean attributeType: ModelAttributeTypes size: ModelSizeInput } input ModelIntInput { ne: Int eq: Int le: Int lt: Int ge: Int gt: Int between: [Int] attributeExists: Boolean attributeType: ModelAttributeTypes } input ModelSizeInput { ne: Int eq: Int le: Int lt: Int ge: Int gt: Int between: [Int] } enum ModelSortDirection { ASC DESC } input ModelStringInput { ne: String eq: String le: String lt: String ge: String gt: String contains: String notContains: String between: [String] beginsWith: String attributeExists: Boolean attributeType: ModelAttributeTypes size: ModelSizeInput } input ModelSubscriptionBooleanInput { ne: Boolean eq: Boolean } input ModelSubscriptionFloatInput { ne: Float eq: Float le: Float lt: Float ge: Float gt: Float between: [Float] in: [Float] notIn: [Float] } input ModelSubscriptionGameSessionFilterInput { id: ModelSubscriptionIDInput level: ModelSubscriptionIntInput durationInSeconds: ModelSubscriptionIntInput createdAt: ModelSubscriptionStringInput updatedAt: ModelSubscriptionStringInput and: [ModelSubscriptionGameSessionFilterInput] or: [ModelSubscriptionGameSessionFilterInput] owner: ModelStringInput } input ModelSubscriptionIDInput { ne: ID eq: ID le: ID lt: ID ge: ID gt: ID contains: ID notContains: ID between: [ID] beginsWith: ID in: [ID] notIn: [ID] } input ModelSubscriptionIntInput { ne: Int eq: Int le: Int lt: Int ge: Int gt: Int between: [Int] in: [Int] notIn: [Int] } input ModelSubscriptionStringInput { ne: String eq: String le: String lt: String ge: String gt: String contains: String notContains: String between: [String] beginsWith: String in: [String] notIn: [String] } input UpdateGameSessionInput { id: ID! level: Int durationInSeconds: Int } type Mutation { createGameSession(input: CreateGameSessionInput!, condition: ModelGameSessionConditionInput): GameSession @aws_iam @aws_cognito_user_pools updateGameSession(input: UpdateGameSessionInput!, condition: ModelGameSessionConditionInput): GameSession @aws_iam @aws_cognito_user_pools deleteGameSession(input: DeleteGameSessionInput!, condition: ModelGameSessionConditionInput): GameSession @aws_iam @aws_cognito_user_pools } type Query { getGameSession(id: ID!): GameSession @aws_iam @aws_cognito_user_pools listGameSessions( id: ID, filter: ModelGameSessionFilterInput, limit: Int, nextToken: String, sortDirection: ModelSortDirection ): ModelGameSessionConnection @aws_iam @aws_cognito_user_pools } type Subscription { onCreateGameSession(filter: ModelSubscriptionGameSessionFilterInput, owner: String): GameSession @aws_subscribe(mutations: ["createGameSession"]) @aws_iam @aws_cognito_user_pools onUpdateGameSession(filter: ModelSubscriptionGameSessionFilterInput, owner: String): GameSession @aws_subscribe(mutations: ["updateGameSession"]) @aws_iam @aws_cognito_user_pools onDeleteGameSession(filter: ModelSubscriptionGameSessionFilterInput, owner: String): GameSession @aws_subscribe(mutations: ["deleteGameSession"]) @aws_iam @aws_cognito_user_pools } ```
dpilch commented 3 weeks ago

@naedx npx ampx generate graphql-client-code generates the client side code necessary for your application. It does not produce the GraphQL schema used by your AppSync API. Amplify does not offer a command to produce the transformed GraphQL schema.

You can use aws appsync get-introspection-schema as you have mentioned previously.

You can also access it through the CDK constructs. In your amplify/backend.ts file:

const backend = defineBackend({
  auth,
  data,
});

console.log(
  // @ts-ignore
  backend.data.resources.cfnResources.cfnGraphqlApi.node.scope.schema.definition
);
naedx commented 2 weeks ago

@naedx npx ampx generate graphql-client-code generates the client side code necessary for your application. It does not produce the GraphQL schema used by your AppSync API. Amplify does not offer a command to produce the transformed GraphQL schema.

Hi @dpilch ! The schema is necessary for my application. Here is my use case:

  1. My application is in Flutter

  2. I use a 3rd party GraphQL client.

I use graphql_flutter + graphql_codegen. They provide a wide range of functionality and work well with AppSync. The codegen listens to schema changes and updates the client code accordingly. These libraries are very popular in the Flutter community so other Flutter + Amplify users are likely to have a similar experience to mine.

  1. As our Gen2 project evolves we frequently need to regenerate the our client to match. The current process requires calling aws appsync get-introspection-schema --api-id XXXXX --format SDL --no-include-directives lib/api/amplify/schema.graphql while manually tracking down the api-id that corresponds to the current sandbox and injecting it into the command, defeating a simple npm script to repeat this process.

  2. I then need to produce a file containing (1) the types from the schema.graphql, (2) the query/mutation/subscription definitions from the npx ampx generate graphql-client-code command and (3) the AppSync defined scalars:

scalar AWSDateTime
scalar AWSTimestamp
scalar AWSJSON

Having a flag to include the schema with what is generated by Amplify would greatly complement the Gen2 developer experience (which is fantastic so far). This would help to streamline the process and make Amplify more accessible developers who need to use 3rd party graphql clients to access functionality not provided by the Amplify client.

dpilch commented 2 weeks ago

I will mark this as a feature request.

naedx commented 1 day ago

I've found a solution to the challenges I had above. Instead of using ampx generate graphql-client-code I'm now using @graphql-codegen/cli. This takes care of all the steps I had before.

I've described the setup here: https://github.com/naedx/amplify-playground/tree/dev/projects/amplify-graphql-codegen