amplify-js icon indicating copy to clipboard operation
amplify-js copied to clipboard

[Datastore] saving with undefined relationships throws `Field: id is required` error

Open charlieforward9 opened this issue 2 years ago • 4 comments

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

DataStore

Amplify Categories

storage, api

Environment information

  System:
    OS: macOS 12.6.2
    CPU: (4) x64 Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
    Memory: 210.45 MB / 8.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 18.16.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 9.6.7 - ~/Documents/Github/Agroview2.0/node_modules/.bin/npm
  Browsers:
    Chrome: 114.0.5735.106
    Safari: 16.2
  npmPackages:
    @fortawesome/fontawesome-svg-core: ^6.4.0 => 6.4.0 
    @fortawesome/free-brands-svg-icons: ^6.4.0 => 6.4.0 
    @fortawesome/free-regular-svg-icons: ^6.3.0 => 6.3.0 
    @fortawesome/free-solid-svg-icons: ^6.4.0 => 6.4.0 
    @fortawesome/react-fontawesome: ^0.2.0 => 0.2.0 
    @reduxjs/toolkit: ^1.9.5 => 1.9.5 
    @reduxjs/toolkit-query:  1.0.0 
    @reduxjs/toolkit-query-react:  1.0.0 
    @types/compression: ^1.7.2 => 1.7.2 
    @types/express: ^4.17.14 => 4.17.17 
    @types/leaflet: ^1.9.3 => 1.9.3 
    @types/node: ^20.2.5 => 20.2.5 
    @types/react: ^18.0.28 => 18.2.8 
    @types/react-dom: ^18.0.3 => 18.0.11 
    @types/react-slider: ^1.3.1 => 1.3.1 
    @typescript-eslint/eslint-plugin: ^5.59.9 => 5.59.9 
    @typescript-eslint/parser: ^5.59.9 => 5.59.9 
    @vitejs/plugin-react: ^3.0.0 => 3.1.0 
    @vitejs/plugin-react-swc: ^3.0.0 => 3.3.2 
    amazon-cognito-identity-js: ^6.2.0 => 6.2.0 
    autoprefixer: ^10.4.14 => 10.4.14 
    aws-amplify: ^5.0.23 => 5.0.23 
    compression: ^1.7.4 => 1.7.4 
    concurrently: ^7.6.0 => 7.6.0 
    cross-env: ^7.0.3 => 7.0.3 
    debounced-drag:  1.0.0 
    eslint: ^8.38.0 => 8.41.0 
    eslint-plugin-react-hooks: ^4.6.0 => 4.6.0 
    eslint-plugin-react-refresh: ^0.3.4 => 0.3.5 
    express: ^4.18.1 => 4.18.2 
    i: ^0.3.7 => 0.3.7 
    leaflet: ^1.9.4 => 1.9.4 
    leaflet-draw: ^1.0.4 => 1.0.4 
    npm: ^9.6.7 => 9.6.7 
    postcss: ^8.4.21 => 8.4.23 
    react: ^18.2.0 => 18.2.0 
    react-dom: ^18.2.0 => 18.2.0 
    react-leaflet: ^4.2.1 => 4.2.1 
    react-leaflet-draw: ^0.20.4 => 0.20.4 
    react-redux: ^8.0.7 => 8.0.7 
    react-slider: ^2.0.4 => 2.0.4 
    react-tabs: ^6.0.1 => 6.0.1 
    recharts: ^2.5.0 => 2.6.2 
    redux: ^4.2.1 => 4.2.1 
    redux-todos-with-undo-example:  0.0.0 
    redux-undo: ^1.0.1 => 1.0.1 
    sirv: ^2.0.2 => 2.0.2 
    tailwindcss: ^3.3.2 => 3.3.2 
    ts-node: ^10.9.1 => 10.9.1 
    typescript: ^5.1.3 => 5.1.3 
    vite: ^4.0.3 => 4.3.9 
  npmGlobalPackages:
    @aws-amplify/cli: 10.2.1
    corepack: 0.17.0
    gh-pages: 3.2.3
    n: 9.1.0
    nodemon: 2.0.13
    npm: 9.5.1
    stable: 0.1.8
    tls-test: 1.0.0
    ts-node: 10.9.1

Describe the bug

Related to #11271 (comment) Using DataStore.save() on a newly instantiated model throws:

Error: Field id is required
    at datastore.ts:580:11
    at datastore.ts:891:7
    at Array.forEach (<anonymous>)
    at datastore.ts:888:28
    at produce (immerClass.ts:94:14)
    at Model.copyOf (datastore.ts:870:18)
    at traverseModel (util.ts:220:39)
    at StorageAdapterBase2.saveMetadata (StorageAdapterBase.ts:186:27)
    at IndexedDBAdapter2.<anonymous> (IndexedDBAdapter.ts:218:9)
    at step (tslib.es6.js:147:23)

Expected behavior

I expect the model to save error-free similarly to how all the other models save. Unsure why this model is now throwing problems while the others are not.

Reproduction steps

  1. Install Amplify Libraries
  2. Create Upload type (see below)
  3. Save this to the data model.

Code Snippet

With this model

type Upload @model @auth(rules: [{ allow: public }]) {
  id: ID!
  datetime: AWSDateTime!
  name: String!
  status: Int!
  type: [Int!]!
  uploadCount: Int!
  processCount: Int!
  Collection: [Collection] @hasMany(indexName: "byUpload", fields: ["id"])
  userID: ID! @index(name: "byUser")
  User: User! @belongsTo(fields: ["userID"])
  RawImages: [RawImage] @hasMany(indexName: "byUpload", fields: ["id"])
}

& this utility function

async function createUpload({
  name,
  status,
  type,
  uploadCount,
  userID,
  User,
  RawImages,
  Collection,
}: {
  name: string;
  status: number;
  type: number[];
  uploadCount: number;
  userID: string;
  User: User;
  RawImages?: RawImage[];
  Collection?: Collection[];
}) {
  try {
    const newUpload = new Upload({
      datetime: new Date().toISOString(),
      name: name,
      status: status,
      type: type,
      uploadCount: uploadCount,
      processCount: 0,
      userID: userID,
      User: User,
      RawImages: RawImages,
      Collection: Collection,
    });
    return await saveModelAPI(newUpload);
  } catch (error) {
    throw error as Error;
  }
}

& this implementation

const name = "unnamed";
const files.length = 0;
upload = await createUpload({
        name: name,
        status: 0,
        type: [0],
        uploadCount: files.length,
        userID: user.id,
        User: user,
      });

Log output

Console logged the model type prior to saving it:

{
    "datetime": "2023-06-15T18:42:41.515Z",
    "name": "asdfg",
    "status": 0,
    "type": [
        0
    ],
    "uploadCount": 12,
    "processCount": 0,
    "userID": "f70dad58-14ce-4485-9d0e-c790ed72fda8",
    "createdAt": null,
    "updatedAt": null
}

I have verified that the user exists by searching the userID in Amplify Studio.

aws-exports.js


// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "us-east-2",
    "aws_appsync_graphqlEndpoint": "https://whw7hqknjzd6hhqdf56nex3lv4.appsync-api.us-east-2.amazonaws.com/graphql",
    "aws_appsync_region": "us-east-2",
    "aws_appsync_authenticationType": "API_KEY",
    "aws_appsync_apiKey": "<KEY>",
    "aws_cognito_identity_pool_id": "<ID>",
    "aws_cognito_region": "us-east-2",
    "aws_user_pools_id": "<ID>",
    "aws_user_pools_web_client_id": "<ID>",
    "oauth": {
        "domain": "auth-agroview-staging.auth.us-east-2.amazoncognito.com",
        "scope": [
            "phone",
            "email",
            "openid",
            "profile",
            "aws.cognito.signin.user.admin"
        ],
        "redirectSignIn": "https://test.agroview.farm/,https://www.test.agroview.farm/,http://localhost:3000/",
        "redirectSignOut": "https://test.agroview.farm/,https://www.test.agroview.farm/,http://localhost:3000/",
        "responseType": "code"
    },
    "federationTarget": "COGNITO_USER_POOLS",
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [
        "GOOGLE",
        "APPLE"
    ],
    "aws_cognito_signup_attributes": [
        "EMAIL"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ],
    "aws_user_files_s3_bucket": "agroview320d41778bdb4fd3a73392917fe8f514195551-staging",
    "aws_user_files_s3_bucket_region": "us-east-2"
};


export default awsmobile;

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

charlieforward9 avatar Jun 15 '23 18:06 charlieforward9

@charlieforward9 - I have a few follow-up questions:

  1. What version of the CLI are you using?
  2. What conflict resolution strategy are you using?
  3. Can you share your full schema?
  4. Can you share the full code snippet for saveModelAPI?
  5. Can you confirm that you are using DataStore and not the API category? You mention DataStore in the ticket title, but API under Amplify Categories.

Thank you!

david-mcafee avatar Jun 17 '23 21:06 david-mcafee

@charlieforward9 - to follow up on this, I attempted to reproduce the issue with the following schema + sample code, and did not encounter the error:

schema.graphql

input AMPLIFY {
  globalAuthRule: AuthRule = { allow: public }
}
type Upload @model {
  id: ID!
  datetime: AWSDateTime!
  name: String!
  status: Int!
  type: [Int!]!
  uploadCount: Int!
  processCount: Int!
  userID: ID! @index(name: "byUser")
}

App.tsx

import { useState } from "react";
import { DataStore, Predicates } from "@aws-amplify/datastore";
import { Upload } from "./models";

function App() {
  const [upload, setUpload] = useState<Upload | null>(null);

  async function saveModelAPI(model: Upload) {
    try {
      const savedModel = await DataStore.save(model);
      console.log("savedModel: ", savedModel);
      setUpload(savedModel);
      return savedModel;
    } catch (error) {
      console.error("saveModelAPI error: ", error);
    }
  }

  async function createUpload({
    name,
    status,
    type,
    uploadCount,
    userID,
  }: {
    name: string;
    status: number;
    type: number[];
    uploadCount: number;
    userID: string;
  }) {
    try {
      const newUpload = new Upload({
        datetime: new Date().toISOString(),
        name: name,
        status: status,
        type: type,
        uploadCount: uploadCount,
        processCount: 0,
        userID: userID,
      });

      console.log("newUpload: ", newUpload);
      return await saveModelAPI(newUpload);
    } catch (error) {
      throw error as Error;
    }
  }

  return (
    <div className="App">
      <header className="App-header">
        <button
          onClick={() =>
            createUpload({
              name: `Upload ${new Date().toISOString()}`,
              status: 1,
              type: [1, 2],
              uploadCount: 1,
              userID: `${Date.now()}`,
            })
          }
        >
          Create Upload
        </button>
        <pre>Current Upload: {JSON.stringify(upload, null, 2)}</pre>
      </header>
    </div>
  );
}

export default App;

david-mcafee avatar Jun 17 '23 22:06 david-mcafee

@david-mcafee Thank you for the elaborate set of questions and attempting to reproduce my issue. I have been messing around with the problem and seem to have narrowed the issue at hand. It stems from the relationships...

When I call createUpload(...) without the // <-- Problematic lines, the model is successfully saved. However, when I add in the optional relationships, the error is thrown:

async function createUpload({
  name,
  status,
  type,
  uploadCount,
  userID,
  User,
  RawImages,
  Collection,
}: {
  name: string;
  status: number;
  type: number[];
  uploadCount: number;
  userID: string;
  User: User;
  RawImages?: RawImage[];
  Collection?: Collection[];
}): Promise<Upload> {
  const upload = new Upload({
    datetime: new Date().toISOString(),
    name: name,
    status: status,
    type: type,
    uploadCount: uploadCount,
    processCount: 0,
    userID: userID,
    User: User,
    RawImages: RawImages ? RawImages : undefined, // <--Problematic
    Collection: Collection ? Collection : undefined, // <--Problematic
  });
  return await dataStoreService.saveModelAPI(upload);
}

Even if I explicitly set RawImages: undefined & Collection: undefined, it throws. This seems to be a bug, since the data model allows the relationship to be undefined, so it should not matter how I am setting the relationships, so long as the type conforms...


With this now known, I do not think it is necessary to provide the ALL requested info. I'll answer the ones that seem important to move forward:

What version of the CLI are you using?

amplify -v: 11.0.3

What conflict resolution strategy are you using?

Auto Merge

Can you confirm that you are using DataStore and not the API category?

Yes, saving the models with DataStore.save(...)

charlieforward9 avatar Jun 19 '23 17:06 charlieforward9

Just unassigned this from me so that this can be picked up for reproduction now that we have additional repro steps (cc @chrisbonifacio)

david-mcafee avatar Nov 15 '23 22:11 david-mcafee