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.36k stars 2.1k forks source link

Can't send AWSJSON array values using aws-amplify and graphql #13298

Open rodrigogs opened 3 weeks ago

rodrigogs commented 3 weeks ago

Before opening, please confirm:

JavaScript Framework

Vue

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.1 CPU: (10) arm64 Apple M1 Pro Memory: 63.16 MB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 18.19.0 - ~/.nvm/versions/node/v18.19.0/bin/node npm: 10.3.0 - ~/.nvm/versions/node/v18.19.0/bin/npm pnpm: 9.0.6 - ~/.nvm/versions/node/v18.19.0/bin/pnpm Browsers: Safari: 17.4.1 npmPackages: @aws-sdk/client-dynamodb: ^3.556.0 => 3.556.0 @aws-sdk/lib-dynamodb: ^3.556.0 => 3.556.0 @mdi/font: ^7.4.47 => 7.4.47 @nuxt/eslint: ^0.3.9 => 0.3.9 @nuxtjs/i18n: ^8.3.0 => 8.3.1 @vee-validate/nuxt: ^4.12.6 => 4.12.6 @vee-validate/yup: ^4.12.6 => 4.12.6 aws-amplify: ^6.0.30 => 6.0.30 date-fns: ^3.6.0 => 3.6.0 nuxt: ^3.11.2 => 3.11.2 vite-plugin-vuetify: ^2.0.3 => 2.0.3 vue: ^3.4.21 => 3.4.25 vue-router: ^4.3.0 => 4.3.2 vuetify: ^3.5.16 => 3.5.17 yup: ^1.4.0 => 1.4.0 npmGlobalPackages: corepack: 0.22.0 gpt-context-feeder: 0.0.1 npm: 10.3.0 pnpm: 9.0.6 tunnelmole: 2.2.14 ```

Describe the bug

I'm developing an application using Nuxt 3 and Amplify.

Expected behavior

I should be able to send a JSON with items to the backend through the graphql API.

Reproduction steps

  1. Create a nuxt project https://nuxt.com/docs/getting-started/installation
  2. Configure Amplify https://nuxt.com/deploy/aws-amplify
  3. Create a schema having some JSON property using Amplify Studio and pull it from the cloud using amplify-cli. Follow Amplify instructions to configure it.
  4. Try to send a JSON with items using the graphql library.

Code Snippet

// Put your code below this line.
import { generateClient } from 'aws-amplify/api'
import { createEvent, updateEvent, deleteEvent } from '~/lib/graphql/mutations'
import { getEvent, listEvents } from '~/lib/graphql/queries'
import type { EventPage } from '~/lib/events/model'
import type { EventType, Event, CreateEventInput, UpdateEventInput, DeleteEventInput } from '~/lib/API'

class EventsDAO {
  private client

  constructor() {
    this.client = generateClient()
  }

  async createEvent(createInput: CreateEventInput): Promise<Event> {
    return this.client.graphql({
      query: createEvent,
      variables: {
        input: createInput,
      },
    }).then(({ data }) => data.createEvent)
  }

  async getEventById(id: string): Promise<Event> {
    return this.client.graphql({
      query: getEvent,
      variables: {
        id,
      },
    }).then(({ data }) => {
      if (!data.getEvent) {
        throw createError({ status: 404, message: 'Event not found' })
      }
      return data.getEvent
    })
  }

  async updateEvent(updateInput: UpdateEventInput): Promise<Event> {
    await this.getEventById(updateInput.id) // Check if event exists

    // TODO: Implement optimistic locking

    return this.client.graphql({
      query: updateEvent,
      variables: {
        input: updateInput,
      },
    }).then(({ data }) => data.updateEvent)
  }

  async deleteEvent(deleteInput: DeleteEventInput): Promise<void> {
    await this.client.graphql({
      query: deleteEvent,
      variables: {
        input: deleteInput,
      },
    }).then(({ data }) => {
      if (!data.deleteEvent) {
        throw createError({ status: 404, message: 'Event not found' })
      }
    })
  }

  async queryEventsByType(type: EventType, nextToken?: string | null): Promise<EventPage> {
    const results = await this.client.graphql({
      query: listEvents,
      variables: {
        filter: {
          type: {
            eq: type,
          },
        },
        nextToken,
      },
    })

    return {
      items: results.data.listEvents.items,
      nextToken: results.data.listEvents.nextToken,
    }
  }
}

export default EventsDAO

Log output

``` // Put your logs below this line ERROR { data: null, errors: [ { path: null, locations: [Array], message: "Variable 'involvedOrgs' has an invalid value." } ] } ```

aws-exports.js

const awsmobile = {
    "aws_project_region": "us-west-1",
    "aws_appsync_graphqlEndpoint": "https://f6koholxxzc5pcqfwmtdjkdlsa.appsync-api.us-west-1.amazonaws.com/graphql",
    "aws_appsync_region": "us-west-1",
    "aws_appsync_authenticationType": "API_KEY",
    "aws_appsync_apiKey": "MY_SECRET_API_KEY"
};

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

rodrigogs commented 3 weeks ago

I could make it work by stringifying the JSON:

eg.:

  async createEvent(createInput: CreateEventInput): Promise<Event> {
    return this.client.graphql({
      query: createEvent,
      variables: {
        input: {
          ...createInput,
          involvedPeople: JSON.stringify(createInput.involvedPeople),
        },
      },
    }).then(({ data }) => data.createEvent)
  }

Given that the GraphQL schema specifies an array, it's logical to anticipate that the generated input would require an array and not a string. Ignoring the type error in the code might be a temporary workaround, but inherently, it doesn't seem appropriate.

chrisbonifacio commented 2 weeks ago

Hi @rodrigogs this seems to be expected behavior. At this point, serialization of the data is up to the client. If you would like for the library to handle serialization, I would recommend opening a feature request.

rodrigogs commented 2 weeks ago

Hi @chrisbonifacio, I appreciate your response, but I must respectfully disagree with the notion that this is expected behavior. The inconsistency arises because the library's generated GraphQL types specify an array for AWSJSON fields, yet the underlying system only accepts a stringified JSON, which leads to confusion and the need for additional client-side handling that one could argue should be intrinsic to the library's functionality.

If the intent is indeed for clients to handle serialization, it would be beneficial for this to be explicitly documented, or better yet, for the type generation to align more closely with the actual data expectations—either by correcting the type definitions or by handling serialization/deserialization within the library itself. This would enhance developer experience and reduce potential errors. Would you consider revisiting this issue in light of this perspective?