aws-amplify / amplify-cli

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development.
Apache License 2.0
2.81k stars 821 forks source link

AWS Amplify GraphQL subscriptions fail with "Cannot return null for non-nullable type: 'AWSDateTime' within parent ... for createdAt/updatedAt" #5542

Closed duncangroenewald closed 2 years ago

duncangroenewald commented 3 years ago

Describe the bug Subscriptions are triggered correctly by fail to return any data because of null values for automatically created 'createAt' and 'updatedAt' fields.

To Reproduce

Expected behavior Subscripts get triggered when new items are created but the returned object is null with the following errors:

"message: "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Video' (/onCreateVideo/createdAt)" "message: "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Video' (/onCreateVideo/updatedAt)"

Code Snippet graphQL schema is as follows - mostly automatically generated by Amplify scripts:

type Video 
@model 
@key(name: "byAlbum", fields: ["albumId"], queryField: "listVideosByAlbum")
@auth(rules: [
  {allow: owner},
  {allow: private, provider: iam}
]) {
    id: ID!
    albumId: ID!
    album: Album @connection(fields: ["albumId"])
    bucket: String!
    fullsize: VideoS3Info!
    thumbnail: VideoS3Info!
}

export const createVideo = /* GraphQL */ `
  mutation CreateVideo(
    $input: CreateVideoInput!
    $condition: ModelVideoConditionInput
  ) {
    createVideo(input: $input, condition: $condition) {
      id
      albumId
      bucket
      fullsize {
        key
        width
        height
      }
      thumbnail {
        key
        width
        height
      }
      createdAt
      updatedAt
      album {
        id
        name
        createdAt
        updatedAt
        owner
        videos {
          nextToken
        }
      }
      owner
    }
  }
`;

export const onCreateVideo = /* GraphQL */ `
  subscription OnCreateVideo($owner: String) {
    onCreateVideo(owner: $owner) {
      id
      albumId
      bucket
      fullsize {
        key
        width
        height
      }
      thumbnail {
        key
        width
        height
      }
      createdAt
      updatedAt
      album {
        id
        name
        createdAt
        updatedAt
        owner
        videos {
          nextToken
        }
      }
      owner
    }
  }
`;

And the React client code

useEffect(() => {

    let subscription
    async function setupSubscription() {
      const user = await Auth.currentAuthenticatedUser()
      subscription = API.graphql(graphqlOperation(subscriptions.onCreateVideo, { owner: user.username })).subscribe({
        next: (data) => {
          const video = data.value.data.onCreateVideo
          console.log(data)
          if (video == null) return
          if (video.albumId !== selectedSectionId) return
          setVideos(p => p.concat([video]))
        }
      })
    }

    setupSubscription()

    return () => subscription?.unsubscribe();
  }, [selectedSectionId])

Screenshots

image

What is Configured? If applicable, please provide what is configured for Amplify CLI:

Environment ```bash System: OS: macOS 10.15.7 CPU: (8) x64 Intel(R) Core(TM) i7-1060NG7 CPU @ 1.20GHz Memory: 384.65 MB / 16.00 GB Shell: 5.7.1 - /bin/zsh Binaries: Node: 12.18.1 - /usr/local/bin/node Yarn: 1.22.10 - /usr/local/bin/yarn npm: 6.14.8 - /usr/local/bin/npm Browsers: Chrome: 85.0.4183.121 Safari: 14.0 npmPackages: @auth0/auth0-spa-js: ^1.12.1 => 1.12.1 @brainhubeu/react-carousel: ^1.19.20 => 1.19.26 @date-io/moment: ^1.3.13 => 1.3.13 @emotion/core: ^10.0.35 => 10.0.35 @emotion/styled: ^10.0.27 => 10.0.27 @material-ui/core: ^4.11.0 => 4.11.0 @material-ui/icons: ^4.9.1 => 4.9.1 @material-ui/lab: ^4.0.0-alpha.56 => 4.0.0-alpha.56 @material-ui/pickers: ^3.2.10 => 3.2.10 @popperjs/core: ^2.5.2 => 2.5.3 @storybook/addon-a11y: ^5.3.19 => 5.3.21 @storybook/addon-actions: ^5.3.19 => 5.3.21 @storybook/addon-knobs: ^5.3.19 => 5.3.21 @storybook/addon-links: ^5.3.19 => 5.3.21 @storybook/addons: ^5.3.19 => 5.3.21 @storybook/preset-create-react-app: ^3.0.0 => 3.1.4 @storybook/react: ^5.3.19 => 5.3.21 @storybook/theming: ^5.3.19 => 5.3.21 @testing-library/jest-dom: ^5.11.4 => 5.11.4 @testing-library/react: ^11.0.4 => 11.0.4 @testing-library/user-event: ^12.1.6 => 12.1.6 @types/d3-geo: ^1.11.1 => 1.12.1 @types/jest: ^26.0.14 => 26.0.14 @types/js-cookie: ^2.2.6 => 2.2.6 @types/material-ui: ^0.21.8 => 0.21.8 @types/node: ^14.11.2 => 14.11.2 @types/react: ^16.9.49 => 16.9.50 @types/react-color: ^3.0.4 => 3.0.4 @types/react-dom: ^16.9.8 => 16.9.8 @types/react-redux: ^7.1.9 => 7.1.9 @types/react-router-config: ^5.0.1 => 5.0.1 @types/react-router-dom: ^5.1.5 => 5.1.5 @types/react-simple-maps: ^1.0.3 => 1.0.3 @types/react-slick: ^0.23.4 => 0.23.4 @types/recharts: ^1.8.13 => 1.8.16 @types/styled-components: ^5.1.3 => 5.1.3 @types/yup: ^0.29.3 => 0.29.7 @typescript-eslint/eslint-plugin: ^4.2.0 => 4.3.0 @typescript-eslint/parser: ^4.2.0 => 4.3.0 amazon-cognito-identity-js: ^4.4.0 => 4.4.0 animate.css: ^4.1.1 => 4.1.1 apexcharts: ^3.19.3 => 3.21.0 array-move: ^3.0.1 => 3.0.1 autoprefixer: ^10.0.0 => 10.0.1 autosuggest-highlight: ^3.1.1 => 3.1.1 aws-amplify: ^3.3.2 => 3.3.3 axios: ^0.20.0 => 0.20.0 axios-mock-adapter: ^1.18.2 => 1.18.2 babel-eslint: ^10.1.0 => 10.1.0 babel-plugin-transform-imports: ^2.0.0 => 2.0.0 babel-plugin-transform-remove-console: ^6.9.4 => 6.9.4 clsx: ^1.1.1 => 1.1.1 connected-react-router: ^6.8.0 => 6.8.0 d3-geo: ^2.0.1 => 2.0.1 date-fns: ^2.16.1 => 2.16.1 downshift: ^6.0.6 => 6.0.6 draft-js: ^0.11.7 => 0.11.7 eslint: ^6.6.0 => 6.8.0 eslint-config-prettier: ^6.12.0 => 6.12.0 eslint-config-react-app: ^5.2.1 => 5.2.1 eslint-plugin-flowtype: ^5.2.0 => 5.2.0 eslint-plugin-import: ^2.22.0 => 2.22.1 eslint-plugin-prettier: ^3.1.4 => 3.1.4 eslint-plugin-react: ^7.21.2 => 7.21.2 eslint-plugin-react-hooks: ^4.1.2 => 4.1.2 firebase: ^7.21.1 => 7.22.0 flag-icon-css: ^3.5.0 => 3.5.0 formik: ^2.1.5 => 2.1.7 husky: ^4.2.3 => 4.3.0 intl-messageformat: ^9.3.9 => 9.3.9 intl-messageformat-parser: ^6.0.7 => 6.0.8 jss: ^10.4.0 => 10.4.0 jss-extend: ^6.2.0 => 6.2.0 jss-rtl: ^0.3.0 => 0.3.0 lint-staged: ^10.0.8 => 10.4.0 lodash: ^4.17.20 => 4.17.20 match-sorter: ^4.2.1 => 4.2.1 material-table: ^1.69.0 => 1.69.1 material-ui-popup-state: ^1.6.1 => 1.6.1 moment: ^2.29.0 => 2.29.0 namor: ^2.0.2 => 2.0.2 notistack: ^1.0.0 => 1.0.0 npm-run-all: ^4.1.5 => 4.1.5 postcss-import: ^12.0.1 => 12.0.1 prettier: ^2.1.2 => 2.1.2 prism-react-renderer: ^1.1.1 => 1.1.1 prop-types: ^15.7.2 => 15.7.2 purgecss: ^1.4.2 => 1.4.2 raw-loader: ^4.0.1 => 4.0.1 rc-queue-anim: ^1.8.3 => 1.8.5 react: ^16.13.1 => 16.13.1 react-apexcharts: ^1.3.7 => 1.3.7 react-autosuggest: ^10.0.2 => 10.0.2 react-beautiful-dnd: ^13.0.0 => 13.0.0 react-big-calendar: ^0.28.0 => 0.28.0 react-bottom-scroll-listener: ^4.1.0 => 4.1.0 react-chat-window: ^1.2.1 => 1.2.1 react-circular-progressbar: ^2.0.3 => 2.0.3 react-code-input: ^3.10.0 => 3.10.0 react-color: ^2.18.1 => 2.18.1 react-daypicker: ^3.0.10 => 3.0.10 react-dnd: ^11.1.3 => 11.1.3 react-dnd-html5-backend: ^11.1.3 => 11.1.3 react-dom: ^16.13.1 => 16.13.1 react-draft-wysiwyg: ^1.14.5 => 1.14.5 react-draggable: ^4.4.3 => 4.4.3 react-dropzone: ^11.2.0 => 11.2.0 react-google-maps: ^9.4.5 => 9.4.5 react-image-timeline: ^3.2.13 => 3.2.13 react-images: ^1.1.7 => 1.1.7 react-intl: ^5.8.2 => 5.8.4 react-notifications-component: ^2.4.1 => 2.4.1 react-number-format: ^4.4.1 => 4.4.1 react-perfect-scrollbar: ^1.5.8 => 1.5.8 react-photo-gallery: ^8.0.0 => 8.0.0 react-player: ^2.6.2 => 2.6.2 react-popper: ^2.2.3 => 2.2.3 react-redux: ^7.2.1 => 7.2.1 react-ripples: ^2.2.1 => 2.2.1 react-router-config: ^5.1.1 => 5.1.1 react-router-dom: ^5.2.0 => 5.2.0 react-router-redux: ^4.0.8 => 4.0.8 react-scripts: ^3.4.3 => 3.4.3 react-select: ^3.1.0 => 3.1.0 react-share: ^4.2.1 => 4.3.0 react-simple-maps: ^2.1.2 => 2.1.2 react-slick: ^0.27.11 => 0.27.11 react-sortable-hoc: ^1.11.0 => 1.11.0 react-spring: ^8.0.27 => 8.0.27 react-svg-piechart: ^2.4.1 => 2.4.1 react-swipeable-views: ^0.13.9 => 0.13.9 react-table: 6.10.3 => 6.10.3 react-text-mask: ^5.4.3 => 5.4.3 react-toastify: ^6.0.8 => 6.0.8 react-tooltip: ^4.2.7 => 4.2.10 react-transition-group: ^4.4.1 => 4.4.1 react-virtualized: ^9.22.2 => 9.22.2 react-window: ^1.8.5 => 1.8.5 recharts: ^1.8.5 => 1.8.5 redux: ^4.0.5 => 4.0.5 redux-thunk: ^2.3.0 => 2.3.0 slick-carousel: ^1.8.1 => 1.8.1 storybook-addon-material-ui: ^0.9.0-alpha.21 => 0.9.0-alpha.21 styled-components: ^5.2.0 => 5.2.0 tslint: ^6.1.2 => 6.1.3 tslint-react: ^5.0.0 => 5.0.0 typescript: ^3.8.3 => 3.9.7 use-url-search-params: ^2.3.13 => 2.3.13 velocity-animate: ^1.5.2 => 1.5.2 velocity-react: ^1.4.3 => 1.4.3 yup: ^0.29.3 => 0.29.3 npmGlobalPackages: @aws-amplify/cli: 4.29.4 amplify-cli: 1.0.0 npm: 6.14.8 typescript: 4.0.3 yarn: 1.22.10 ```

Smartphone (please complete the following information):

Additional context N/A

yuth commented 3 years ago

@duncangroenewald what version of Amplify CLI do you have installed?

Could you update your Video type to the following and see if the error goes away

type Video 
@model 
@key(name: "byAlbum", fields: ["albumId"], queryField: "listVideosByAlbum")
@auth(rules: [
  {allow: owner},
  {allow: private, provider: iam}
]) {
    id: ID!
    albumId: ID!
    album: Album @connection(fields: ["albumId"])
    bucket: String!
    fullsize: VideoS3Info!
    thumbnail: VideoS3Info!
    createdAt: AWSDateTime
    updatedAt:AWSDateTime
}
LoganArnett commented 3 years ago

I am getting a similar issue with a connection to a model not allowing me to grab the nested values

type User @model(subscriptions: null) {
  id: ID!
  firstName: String!
  lastName: String!
  email: String!
  messages: [Message] @connection(keyName: "byAuthor", fields: ["id"])
}

type Message @model(subscriptions: null, queries: null) {
  id: ID!
  content: String!
  authorId: ID!
  author: User! @connection(fields: ["authorId"])
  conversationId: ID!
}

type Subscription {
  onCreateMessage(conversationId: ID!): Message @aws_subscribe(mutations: ["createMessage"])
}

and I get the error: "Cannot return null for non-nullable type: 'User' within parent 'Message' (/onCreateMessage/author)"

yuth commented 3 years ago

@LoganArnett It looks like your schema expect the author to be non-nullable, and the Message does not seem to have an valid author. This could either be because the createMessage passed an non-existent authorId or the Author with that authorId was delete from User table.

LoganArnett commented 3 years ago

I am not sure what happened but I edited another model and pushed and it worked so maybe somehow I was out of sync, thanks @yuth

yuth commented 3 years ago

Please feel free to comment here if you need any further clarification

duncangroenewald commented 3 years ago

@duncangroenewald what version of Amplify CLI do you have installed?

Could you update your Video type to the following and see if the error goes away

type Video 
@model 
@key(name: "byAlbum", fields: ["albumId"], queryField: "listVideosByAlbum")
@auth(rules: [
  {allow: owner},
  {allow: private, provider: iam}
]) {
    id: ID!
    albumId: ID!
    album: Album @connection(fields: ["albumId"])
    bucket: String!
    fullsize: VideoS3Info!
    thumbnail: VideoS3Info!
    createdAt: AWSDateTime
    updatedAt:AWSDateTime
}

@yuth - sorry for the delayed response been tied down with some other things. I will give that a try and let you know how it goes. Thanks

BTW will these still be auto-populated by AppSync if I defined them in the schema? I guess I'll find out...

duncangroenewald commented 3 years ago

@yuth - I just tried that but the returned object is still null.

If I turn AppSync logging on I should be able to see the error in CloudWatch right?

duncangroenewald commented 3 years ago

Hmm nothing to see in CloudWatch!

But no errors reported now in the browser console.

image

Amplify 4.29.8

yuth commented 3 years ago

Could you try running the subscription and mutation in the AppSync console. This should make it easier to debug as you can narrow down if the problem is in AppSync resolver or in the Javascript side of things

BTW will these still be auto-populated by AppSync if I defined them in the schema? I guess I'll find out...

It should add the autopupulate createdAt and updatedAt if no value was passed in the input

duncangroenewald commented 3 years ago

This subscription seems to work fine in the console:

subscription MySubscription {
  onCreateVideo {
    id
      bucket
      key
      sessionDate
      isReviewed
      reviewedBy
      reviewDate
      title
      comments
      sessionTypeID
      activityTypeID
      modeID
      genderID
      ageGroupID
      weightCategoryID
      athletes {
        items {
          id
          createdAt
          updatedAt
        }
        nextToken
      }
      createdAt
      updatedAt
      sessionType {
        id
        name
        activityTypes {
          nextToken
        }
        modes {
          nextToken
        }
        createdAt
        updatedAt
        videos {
          nextToken
        }
      }
      owner
  }
}

However the definition is slightly different to the one used in the app which has the $owner parameter - and I am not sure I understand the distinction. I an going to try a customised subscription without the $owner parameter in the app.

export const onCreateVideo = /* GraphQL */ `
  subscription OnCreateVideo($owner: String) {
    onCreateVideo(owner: $owner) {
      id
      bucket
      key
      sessionDate
      isReviewed
      reviewedBy
      reviewDate
      title
      comments
      sessionTypeID
      activityTypeID
      modeID
      genderID
      ageGroupID
      weightCategoryID
      athletes {
        items {
          id
          createdAt
          updatedAt
        }
        nextToken
      }
      createdAt
      updatedAt
      sessionType {
        id
        name
        activityTypes {
          nextToken
        }
        modes {
          nextToken
        }
        createdAt
        updatedAt
        videos {
          nextToken
        }
      }
      owner
    }
  }
`;
duncangroenewald commented 3 years ago

So in the end I got rid of the $owner in the schema definition - which fixed the problem. I guess it wasn't happy that I was not passing in the owner parameter so couldn't send the data but could still trigger the event.

johnEthicalTechnology commented 3 years ago

I'm having a similar issue with subscriptions. However, it isn't working in the AppSync console either.

This is the error:

"data": {
    "onUpdateLead": null
  },
  "errors": [
    {
      "message": "Cannot return null for non-nullable type: 'String' within parent 'Lead' (/onUpdateLead/formId)",
      "path": [
        "onUpdateLead",
        "formId"
      ]
    },
    {
      "message": "Cannot return null for non-nullable type: 'DeliveryStatus' within parent 'Lead' (/onUpdateLead/deliveryStatus)",
      "path": [
        "onUpdateLead",
        "deliveryStatus"
      ]
    }
  ]

These is the error I get in my app when a subscription returns. Also, just like in the AppSync console message no object is being returned.

0: Object { message: "Cannot return null for non-nullable type: 'String' within parent 'Lead' (/onUpdateLead/formId)", path: (2) […] }
1: Object { message: "Cannot return null for non-nullable type: 'String' within parent 'Lead' (/onUpdateLead/pageId)", path: (2) […] }
2: Object { message: "Cannot return null for non-nullable type: 'Status' within parent 'Lead' (/onUpdateLead/status)", path: (2) […] }
3: Object { message: "Cannot return null for non-nullable type: 'DeliveryStatus' within parent 'Lead' (/onUpdateLead/deliveryStatus)", path: (2) […] }
4: Object { message: "Cannot return null for non-nullable type: 'ID' within parent 'Lead' (/onUpdateLead/mappingDataID)", path: (2) […] 
5: Object { message: "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Lead' (/onUpdateLead/createdAt)", path: (2) […] 
6: Object { message: "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Lead' (/onUpdateLead/updatedAt)", path: (2) […] }

If I don't include any of the non-nullable fields in the return of the subscription the ID returns but any other field returns null. Why is that?

Schema

type Lead
  @model
  @key(name: "byMappingData", fields: ["mappingDataID"])
  @auth(
    rules: [
      { allow: private, operations: [update, read] }
      { allow: private, provider: iam, operations: [create, read, update] }
    ]
  ) {
  id: ID!
  formId: String!
  pageId: String!
  processedLeadData: String
  status: Status!
  deliveryStatus: DeliveryStatus!
  zohoResponse: String
  createdTime: String
  fbLeadId: String
  rawLeadData: [FieldData]
  mappingDataID: ID!
  submitDataToZoho: Boolean
  mappingData: MappingData @connection(fields: ["mappingDataID"])
}

This is the subscription I'm using:

export const onUpdateLead = /* GraphQL */ `
  subscription OnUpdateLead {
    onUpdateLead {
      id
      formId
      pageId
      processedLeadData
      status
      deliveryStatus
      zohoResponse
      createdTime
      fbLeadId
      rawLeadData {
        name
        values
      }
      mappingDataID
      submitDataToZoho
      createdAt
      updatedAt
      mappingData {
        id
        formName
        formId
        pageId
        mappingInfo {
          forcedFields
        }
        rawLeadFields {
          key
          label
          type
          id
          conditional_questions_group_id
        }
        status
        formCreatedTime
        deliveryStatus
        errors
        campaignName
        campaignId
        archived
        createdAt
        updatedAt
        leads {
          nextToken
        }
      }
    }
  }
`;

When I use the updateLead mutation it returns whatever fields are selected to be returned. I don't understand why the subscription isn't doing this. Am I missing something?

I'm using 4.30.0 of the Amplify CLI.

aleksvidak commented 3 years ago

I had the similar problem and what I realised is that mutation which in fact triggers the subscription has to have the same response fields as the ones specified for the subscription response. This way it works for me.

danibrear commented 3 years ago

I've run into this issue and have tried 10 different ways to solve it. my object returned was:

type Checkin
  @model
  @searchable
  @key(name: "byUserId", fields: ["userId"], queryField: "checkinByUserId")
  @auth(
    rules: [
      { allow: owner }
      { allow: private, operations: [read] }
      { allow: private, provider: iam, operations: [create, read, update] }
    ]
  ) {
  comment: String
  createdAt: AWSDateTime!
  time: AWSDateTime!
  id: ID!
  userId: ID!
  team: Team @connection(name: "CheckinOnTeam")
  game: Game! @connection(name: "CheckinsOnGame")
  updatedAt: AWSDateTime!
  user: User! @connection(name: "CheckinOnUser")
  venue: Venue @connection(name: "CheckinOnVenue")
  isPrivate: Boolean
  atHome: Boolean
}

and I have a mutation:

updateCheckinStats(
    action: String!
    checkinId: String!
    numCheers: Int
    numComments: Int
  ): Checkin! @function(name: "updateCheckinStats-${env}")

When I run the query in the api console it still get:

Cannot return null for non-nullable type: 'Game' within parent 'Checkin' (/updateCheckinStats/game)
Cannot return null for non-nullable type: 'User' within parent 'Checkin' (/updateCheckinStats/user)

I've punted on it and just having it return boolean if the update was successful but this is super annoying.

fakedob commented 3 years ago

I had the similar problem and what I realised is that mutation which in fact triggers the subscription has to have the same response fields as the ones specified for the subscription response. This way it works for me.

I was just trying to figure out the same and it looks like @aleksvidak was correct. It is not an expected behaviour, but works, so thanks 👍

BTW it works correctly if you use amplify mock to test localy 🤷‍♂️

BernhardSmuts commented 3 years ago

I had the similar problem and what I realised is that mutation which in fact triggers the subscription has to have the same response fields as the ones specified for the subscription response. This way it works for me.

This worked for me as well.... So strange...

tjgriffi commented 3 years ago

I'm running into this issue as well and just to be clear on what seems to be working (as specified by @aleksvidak ). You all had to update the subscription response fields for example:

input UpdateExampleModelInput { first_name: String last_name: String email: String }

to include the createdAt/updatedAt fields? Would that just mean modifying these inputs? ex:

input UpdateExampleModelInput { first_name: String last_name: String email: String createdAt: AWSDateTime! updatedAt: AWSDateTime! }

Or is there some other response field that I should be looking at somewhere in the schema/in AWS?

cybercussion commented 3 years ago

I ran into this as well, I had a case where I'm setting a JSON object on a field that I don't really have a way to 'type' since it varies (properties and arrays). Is there a graphQL type other than AWSJSON that could allow for 'any' or something to satisfy it? I guess after a few edits its using a string now so I can live with it. I had a case where I'm updating DynamoDB from S3 and from the UI so I'm trying to keep these in sync.

My issue was a accidental enablement of Conflict Detection. I removed that and now I'm in good shape.

patrickcze commented 3 years ago

I have been able to resolve this issue with some of the comments in https://github.com/aws-amplify/amplify-cli/issues/401, however this cases an issue when using subscriptions where the _lastChangedAt value is not being set it seems.

DataStore - Skipping incoming subscription. Messages: Cannot return null for non-nullable type: 'AWSTimestamp' within parent 'GroundHazardV1' (/onUpdateGroundHazardV1/_lastChangedAt)
danrivett commented 3 years ago

Update: I found out rather embarassingly that I had a stale Lambda deployed which was using an old GraphQL mutation without the newly required field which was the root cause of my issue, so the following can be disgarded:

I ran into what looks like the same issue as reported here. I'm confused by the proposed workaround by @aleksvidak that seems to help others.

The mutation I'm using is generated through amplify codegen and includes in the response all the fields, including the one where the subscription is failing with this "cannot return null for non-nullable type".

The input to that mutation doesn't contain the field since it's not being modified, but the response from the mutation does, which I believe should be sufficient for the subscription to work correctly.

Now in my case the object that fails is an array and is empty, so I'm not sure if that's the edge case here why it's not working.

On the subscription side of things it's using DataStore in a React Native app, so the subscription is generated automatically.

I'm going to look further into whether there's an issue receiving an empty array in the subcription. Could be a red herring but I know empty arrays have caused issues in the past.

danrivett commented 3 years ago

Update: I found out rather embarassingly that I had a stale Lambda deployed which was using an old GraphQL mutation without the newly required field which was the root cause of my issue, so the following can be disgarded:

Screen Shot 2021-03-20 at 1 24 30 PM

To clarify, this is the error I see. The Worker was mutated, the mutation response contained an field - remainingOnboardingActivities - which is an object type, and that object wasn't null but contains 2 fields which are arrays, and both aren't null but empty.

type Worker {
   remainingOnboardingActivities: OnboardingActivities! # <- this field is not null
}

type OnboardingActivities {
  inApp: [OnboardingActivity!]! # <- this field is empty
  external: [OnboardingActivity!]  # <- this field is empty
}

type OnboardingActivity {
  id: String!
  title: String
}

Here's what I see when I log out the response from the mutation (update: this was from a different mutation, the one causing the warning above didn't include those fields but I didn't see it in the logs):

{
    "data": {
        "updateWorker": {
            ...
            "remainingOnboardingActivities": {
                "inApp": [],
                "external": [],
                "__typename": "OnboardingActivities"
            },
            ...
        }
    }
}

So the remainingOnboardingActivities is not null. If this is a separate issue I can create a separate ticket as don't want to hijack, but as this ticket has remained open for a long time, I thought adding extra information that may be related may help resolve it?

ThomasRooney commented 3 years ago

@patrickcze 👍: I ran into this too. The _version and _lastChangedAt starting coming through as null in onCreateX mutations, which caused them to error out. This unfortunately broke DataStore.

To fix, I monkey patched the DynamoDBModelTransformer, removing the wrapNonNull here: https://github.com/aws-amplify/amplify-cli/blob/master/packages/graphql-dynamodb-transformer/src/DynamoDBModelTransformer.ts#L213

-        makeField('_version', [], wrapNonNull(makeNamedType('Int'))),
+        makeField("_version", [], makeNamedType("Int")),
-        makeField('_lastChangedAt', [], wrapNonNull(makeNamedType('AWSTimestamp'))),
+        makeField("_lastChangedAt", [], makeNamedType("AWSTimestamp")),

Seems to resolve the problem, though I don't quite know where it came from. Anecdotally, this only started happening to me when I ejected my project from the amplify cli and started using the graphql resolver libraries directly (primarily using https://github.com/kcwinner/cdk-appsync-transformer)

jollyjunkaih commented 3 years ago

I had the similar problem and what I realised is that mutation which in fact triggers the subscription has to have the same response fields as the ones specified for the subscription response. This way it works for me.

Hi, can someone explain to me what this means practically (like which part of my mutations should i change to get my subscription to return items instead of null)

Thanks

jgroom33 commented 3 years ago

For me, I resolved the null issue by removing (owner: $owner) and ($owner: String) from src/graphql/subscriptions.js

However, my use case executes the mutation using an API key. In that scenario, the the mutation is not passing the owner prop.

mogarick commented 3 years ago

In summary, most of the time the behavior reported in this issue is due to the way subscriptions work. If the behavior is different then it is indeed an error.

What @aleksvidak said is kind of expected.

I had the similar problem and what I realised is that mutation which in fact triggers the subscription has to have the same response fields as the ones specified for the subscription response. This way it works for me.

According to the AppSync docs this seems to be the expected behavior.

Subscriptions are triggered from mutations and the mutation selection set is sent to subscribers.

And this part is clearer:

If you use the pure WebSockets client, selection set filtering is done per client, as each client can define its own selection set. In this case, the subscription selection set must be a subset of the mutation selection set. For example, a subscription addedPost{author title} linked to the mutation addPost(...){id author title url version} receives only the author and title of the post. It does not receive the other fields. However, if the mutation lacked the author in its selection set, the subscriber would get a null value for the author field (or an error in case the author field is defined as required/not-null in the schema).

Hope this can help or give a hint to other devs landing at this issue. :)

alrashedf commented 3 years ago

In our react app, we were getting the following error:

DataStore - Skipping incoming subscription. Messages: Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Orders' (/onCreateOrders/createdAt) Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Orders' (/onCreateOrders/updatedAt)

By removing the following from src/models/schema.js:

                ...
                "createdAt": {
                    "name": "createdAt",
                    "isArray": false,
                    "type": "AWSDateTime",
                    "isRequired": false,
                    "attributes": [],
                    "isReadOnly": true
                },
                "updatedAt": {
                    "name": "updatedAt",
                    "isArray": false,
                    "type": "AWSDateTime",
                    "isRequired": false,
                    "attributes": [],
                    "isReadOnly": true
                }
                ...

We were able to subscribe and get updates on Orders. Amplify: 5.3.0

priyanhsu10 commented 2 years ago

Remove the createdAt and updatedAt from the queries.js it solve for me try this

                          `export const getStory = /* GraphQL */ `

query GetStory($id: ID!) { getStory(id: $id) { id title story_type story_text filename createtime // createdAt --->remove this // updatedAt--->remove this } } ; export const listStories = /* GraphQL */ query ListStories( $filter: ModelStoryFilterInput $limit: Int $nextToken: String ) { listStories(filter: $filter, limit: $limit, nextToken: $nextToken) { items { id title story_type story_text filename createtime // createdAt --->remove this // updatedAt--->remove this } nextToken } } ;

klubot-dev commented 2 years ago

I have a very similar issue and spend a complete day trying to figure it out. In a response (I don't know if here or another place), I figured out that the Subscription will inform you the same response you put in the Create statement.

I was just putting the id in the create statement, and in the Subscription asking some other attributes. Adding all the attributes (or at least the needed by the subscription) corrected my problem.

I hope this comment to save some time for more people

AuboIoT commented 2 years ago

I hope this can help: Code before modification in file subscriptions.js:

export const onCreateTruckData = /* GraphQL */ subscription OnCreateTruckData($truckId: String!) { onCreateTruckData(truckId: $truckId) { id truckId latitude longitude createdAt updateAt } } ;

Code after modification in file subscriptions.js:

export const onCreateTruckData = /* GraphQL */ subscription OnCreateTruckData($truckId: String!) { onCreateTruckData(truckId: $truckId) { id truckId latitude longitude } } ;

Basically, I erase the fields createAt and updateAt, because this returns null value and does not allow me to access the nested values. In the AWS AppSync Console works without problems, but gave problems when those values were read in my app. With that modification, worked. If I need the time, I think I can get it inside a Lambda function.

cjihrig commented 2 years ago

I'm going to close this issue, as it can be difficult to triage issues that are several years old, and I believe this one has been answered. If you are still having issues, please open a new issue.

For anyone landing here in the future, these are the relevant docs. Particularly this paragraph:

In this case, the subscription selection set must be a subset of the mutation selection set. For example, a subscription addedPost{author title} linked to the mutation addPost(...){id author title url version} receives only the author and title of the post. It does not receive the other fields. However, if the mutation lacked the author in its selection set, the subscriber would get a null value for the author field (or an error in case the author field is defined as required/not-null in the schema).

bnjr commented 2 years ago

I have a very similar issue and spend a complete day trying to figure it out. In a response (I don't know if here or another place), I figured out that the Subscription will inform you the same response you put in the Create statement.

I was just putting the id in the create statement, and in the Subscription asking some other attributes. Adding all the attributes (or at least the needed by the subscription) corrected my problem.

I hope this comment to save some time for more people

Thanks for this...it makes it work!

HenryUxDesugner commented 1 year ago

I have a very similar issue and spend a complete day trying to figure it out. In a response (I don't know if here or another place), I figured out that the Subscription will inform you the same response you put in the Create statement.

I was just putting the id in the create statement, and in the Subscription asking some other attributes. Adding all the attributes (or at least the needed by the subscription) corrected my problem.

I hope this comment to save some time for more people

I followed your advice and it worked for me. Thank you so much. It so nice that you shared your knowledge to help others.

rafaelrcamargo commented 1 year ago

I have a very similar issue and spend a complete day trying to figure it out. In a response (I don't know if here or another place), I figured out that the Subscription will inform you the same response you put in the Create statement.

Awesome, thanks for that! 🎉

Also I wanna point that this also applies for the update mutations, so basically as I understood the subscription can return all the fields the last modification also returned.

rmazzine commented 5 months ago

In case you are using a lambda to make a custom mutation and you are receiving cannot return null for non-nullable type: param1 ... in the frontend, here what I did to get around it:

It does not work if you just pass the parameters in the lambda function response, like:


const outputReponse = {
  data: {
    customMutation : {
      param1: "abc1",
      param2: "abc2"
    }
  }
}

  return {
      //  Uncomment below to enable CORS requests
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "*"
    }, 
    statusCode: 200,
    body: JSON.stringify(outputReponse)
  }

what you must to do, instead, is to send the parameters names, directly using the callback function of the handler function, like this below:

export const handler = async (event, _, callback) => {
...
callback(null, {param1: "abc1", param2: "abc2"})
  return {
      //  Uncomment below to enable CORS requests
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "*"
    }, 
    statusCode: 200,
    body: JSON.stringify(outputReponse)
  }
Laplacian42 commented 2 weeks ago

I had the same error in Amplify v2 and did not know how to integrate the solutions mentioned here into that framework. However what did help was to simply exclude the nasty fields during subscription with "selectionSet", e.g.:

this.client.models.Season.observeQuery({ selectionSet: ["name", "id"], }).subscribe({ next: ({ items, isSynced }) => { this.seasons = items; }, });