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

Deleting model from DynamoDB failed with Datastore and AppSync, but works with AppSync + _version #11227

Closed Bryson14 closed 1 year ago

Bryson14 commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

GraphQL API, DataStore

Amplify Categories

auth, api

Environment information

``` # Put output below this line System: OS: Linux 5.15 Ubuntu 22.04.1 LTS 22.04.1 LTS (Jammy Jellyfish) CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz Memory: 4.04 GB / 7.66 GB Container: Yes Shell: 5.1.16 - /bin/bash Binaries: Node: 18.14.0 - /usr/local/bin/node Yarn: 1.22.19 - /mnt/c/Users/Bryson/AppData/Roaming/npm/yarn npm: 9.4.2 - /usr/local/bin/npm npmPackages: @aws-amplify/ui-react: ^4.3.8 => 4.3.8 @aws-amplify/ui-react-internal: undefined () @paypal/react-paypal-js: ^7.8.2 => 7.8.2 @testing-library/jest-dom: ^5.16.5 => 5.16.5 @testing-library/react: ^13.4.0 => 13.4.0 @testing-library/user-event: ^13.5.0 => 13.5.0 @types/jest: ^27.5.2 => 27.5.2 @types/node: ^16.18.12 => 16.18.12 @types/react: ^18.0.28 => 18.0.28 @types/react-dom: ^18.0.11 => 18.0.11 @types/react-modal: ^3.13.1 => 3.13.1 aws-amplify: ^5.0.15 => 5.0.15 js-levenshtein: ^1.1.6 => 1.1.6 react: ^18.2.0 => 18.2.0 react-dom: ^18.2.0 => 18.2.0 react-modal: ^3.16.1 => 3.16.1 react-router-dom: ^6.8.1 => 6.8.1 react-scripts: 5.0.1 => 5.0.1 typescript: ^4.9.5 => 4.9.5 web-vitals: ^2.1.4 => 2.1.4 npmGlobalPackages: @aws-amplify/cli: 10.7.2 corepack: 0.15.3 n: 9.0.1 npm: 9.4.2 serve: 14.2.0 ```

Describe the bug

i have one object Booking that I am trying to delete which has relation keys to two other models Tutor and Client:

type Client @model @auth(rules: [{allow: groups, groups: ["admins"], operations: [read, create, update, delete]}, {allow: private}, {allow: public, operations: [read]}]) {
  id: ID!
  email: AWSEmail!
  firstName: String!
  lastName: String!
  authToken: String!
  phone: AWSPhone
  credits: Float!
  Students: [Student] @hasMany(indexName: "byClient", fields: ["id"])
}
type Tutor @model @auth(rules: [{allow: public, operations: [read, create, update]}, {allow: groups, groups: ["admins"], operations: [read, create, update, delete]}, {allow: groups, groups: ["tutors"], operations: [read, update, delete, create]}, {allow: private}]) {
  id: ID!
  email: AWSEmail!
  firstName: String!
  lastName: String!
  authToken: String!
  phone: AWSPhone
  isRemote: Boolean!
  isInPerson: Boolean!
  hourlyRate: Float!
  grade: String!
  isSearchable: Boolean!
  isActive: Boolean!
  location: String
  resumeUrl: String
  transcriptUrl: String
  photoUrl: String
  bio: String
  subjects: [String]
  schoolAttended: String
}
type Booking @model @auth(rules: [{allow: private}]) {
  id: ID!
  startDateTime: AWSDateTime!
  duration: Int!
  Tutor: Tutor! @hasOne
  bookingCode: String!
  amount: Float!
  discount: Float!
  clientNotes: String
  tutorNotes: String
  tutorPaid: Boolean!
  clientPaid: Boolean!
  resources: String
  Client: Client @hasOne
  studentName: String
  tutorRating: Int
  clientRating: Int
  bookingType: String
  tutorPayAmount: Float
  tip: Float
  transactionId: String
  tutorMileage: Float
  meetingUrl: AWSURL
  hwFiles: [String]
  resourceFiles: [String]
  meetingInstructions: String
  sessionTaught: Boolean
}

When I try to delete the booking model using await Datastore.delete(Booking, bookingId);. I get a failure of object not found:

Error: Namespace Resolver for 'Object' not found! This is probably a bug in '@amplify-js/datastore'. at IndexedDBAdapter.namespaceResolver (http://localhost:3000/static/js/bundle.js:84021:11) at IndexedDBAdapter.getIndexKeyValuesFromModel (http://localhost:3000/static/js/bundle.js:88981:30) at IndexedDBAdapter.<anonymous> (http://localhost:3000/static/js/bundle.js:90170:30) at step (http://localhost:3000/static/js/bundle.js:229816:17) at Object.next (http://localhost:3000/static/js/bundle.js:229765:14) at fulfilled (http://localhost:3000/static/js/bundle.js:229724:24)
message
: 
"Namespace Resolver for 'Object' not found! This is probably a bug in '@amplify-js/datastore'."
stack
: 

When I try to delete uing the GraphQL API, I get a similar conflicting error:

const bookingDetails: DeleteBookingInput = {
                id: selectedBookingId,
              };
              const deletedBooking = await API.graphql<
                GraphQLQuery<DeleteBookingMutation>
              >({
                query: mutations.deleteBooking,
                variables: { input: bookingDetails },
              });

image

There is some sort of conflict that is happening behind the scenes. After a bit of investigating, I found that I can pass the _version of the model to the GraphQL API and get the model to delete.

Now changing the code to this, the model was deleted successfully from the database.

const book = await DataStore.query(Booking, selectedBookingId);
              if (!book) {
                alert(
                  `unable to delete item ${selectedBookingId}. Try again later`
                );
                return;
              }
              const bookingDetails: DeleteBookingInput = {
                id: selectedBookingId,
                // @ts-ignore
                _version: book._version,
              };

              const deletedBooking = await API.graphql<
                GraphQLQuery<DeleteBookingMutation>
              >({
                query: mutations.deleteBooking,
                variables: { input: bookingDetails },
              });

Expected behavior

I expect that calling DataStore.delete() should work as intended and should pass the _version to its AppSync API backend also.

Reproduction steps

  1. Create models with the template above
  2. Create instances of the models in the database,
  3. try to delete from the client side using DataStore, GraphQl without _version, and GraphQl with _version

Code Snippet

// Put your code below this line.
try {
              debugger;
              const book = await DataStore.query(Booking, selectedBookingId);
              if (!book) {
                alert(
                  `unable to delete item ${selectedBookingId}. Try again later`
                );
                return;
              }
              const bookingDetails: DeleteBookingInput = {
                id: selectedBookingId,
              };

              const deletedBooking = await API.graphql<
                GraphQLQuery<DeleteBookingMutation>
              >({
                query: mutations.deleteBooking,
                variables: { input: bookingDetails },
              });
              if (deletedBooking.data?.deleteBooking) {
                alert("Booking deleted");
                setSelectedBookingId("");
                setCanDeleteBooking(false);
              } else {
                alert(
                  `unable to delete item ${selectedBookingId}. Try again later`
                );
              }
            } catch (error) {
              alert(
                `unable to delete item ${selectedBookingId}. Try again later\n${JSON.stringify(
                  error
                )}`
              );
            }
try {
              debugger;
              const book = await DataStore.query(Booking, selectedBookingId);
              if (!book) {
                alert(
                  `unable to delete item ${selectedBookingId}. Try again later`
                );
                return;
              }
              const deletedBooking = DataStore.delete(book);
              });
              if (deletedBooking.data?.deleteBooking) {
                alert("Booking deleted");
                setSelectedBookingId("");
                setCanDeleteBooking(false);
              } else {
                alert(
                  `unable to delete item ${selectedBookingId}. Try again later`
                );
              }
            } catch (error) {
              alert(
                `unable to delete item ${selectedBookingId}. Try again later\n${JSON.stringify(
                  error
                )}`
              );
            }

Log output

``` // Put your logs below this line ```

aws-exports.js

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

const awsmobile = {
    "aws_project_region": "us-east-1",
    "aws_cloud_logic_custom": [
        {
            "name": "AdminQueries",
            "endpoint": "https://zo19r97osi.execute-api.us-east-1.amazonaws.com/dev",
            "region": "us-east-1"
        }
    ],
    "aws_appsync_graphqlEndpoint": "https://a77f2uejlbhudbftfqy7pyhuki.appsync-api.us-east-1.amazonaws.com/graphql",
    "aws_appsync_region": "us-east-1",
    "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
    "aws_appsync_apiKey": "da2-4ndhubp54jfgrkprefllakib3i",
    "aws_cognito_identity_pool_id": "us-east-1:5d0c9a78-048a-4c8f-9e11-64deff99ad1f",
    "aws_cognito_region": "us-east-1",
    "aws_user_pools_id": "us-east-1_mBF6REdpi",
    "aws_user_pools_web_client_id": "a7ati2n1tlk2kd8fqeegfc7ct",
    "oauth": {},
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "EMAIL",
        "NAME"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": [
            "REQUIRES_LOWERCASE",
            "REQUIRES_NUMBERS",
            "REQUIRES_SYMBOLS",
            "REQUIRES_UPPERCASE"
        ]
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ],
    "aws_user_files_s3_bucket": "tmtamplifyapp-storage-c3cc73b4102934-dev",
    "aws_user_files_s3_bucket_region": "us-east-1"
};

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

dpilch commented 1 year ago

Hi @Bryson14, it is expected that the API.graphql call would not work without _version passed when conflict resolution is enabled. However, the error you get from DataStore.delete is unusual.

The error would indicate that Booking passed in DataStore.delete is not a model constructor. What is odd is that this case should be detected and throw a different error.

Is Booking imported like import { Booking } from './models';?

chrisbonifacio commented 1 year ago

Hi 👋 Closing this as we have not heard back from you. If you are still experiencing this issue and in need of assistance, please feel free to comment and provide us with any information previously requested by our team members so we can re-open this issue and be better able to assist you.

Thank you!

chrisbonifacio commented 1 year ago

For what it's worth, I was unable to reproduce the issue with the given reproduction steps. I was able to delete records by querying a record and passing the model to DataStore.delete.

Example:

  const deleteTodo= async () => {
    try {
      const toDelete = (await DataStore.query(
        Todo,
        "385cd611-8e2e-40d1-9e6d-09dae7cce754"
      )) as Todo;

      const deletedTodo= await DataStore.delete(toDelete);

      console.log({ deletedTodo });
    } catch (error) {
      console.log(error);
    }
  };

Intentionally querying for a record with an invalid id, resulting in an undefined value passed to DataStore.delete(Todo, <invalid-id>) throws the following error:

image

chrisbonifacio commented 1 year ago

For the namespace resolver error, upgrading to the latest version of aws-amplify should resolve that issue. If you haven't already done so, please try upgrading and let us know if the issue persists.