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
89 stars 77 forks source link

Api `hasMany` relationships are inconsistently retrieved #2459

Closed AakashKB closed 5 months ago

AakashKB commented 6 months ago

Environment information

System:
  OS: macOS 14.4.1
  CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
  Memory: 2.78 GB / 32.00 GB
  Shell: /bin/zsh
Binaries:
  Node: 20.11.1 - ~/.nvm/versions/node/v20.11.1/bin/node
  Yarn: 1.22.22 - ~/.nvm/versions/node/v20.11.1/bin/yarn
  npm: 10.2.4 - ~/.nvm/versions/node/v20.11.1/bin/npm
  pnpm: 8.10.5 - /usr/local/bin/pnpm
NPM Packages:
  @aws-amplify/backend: 0.13.0-beta.14
  @aws-amplify/backend-cli: 0.12.0-beta.16
  aws-amplify: 6.0.20
  aws-cdk: 2.133.0
  aws-cdk-lib: 2.133.0
  typescript: 5.4.2
AWS environment variables:
  AWS_STS_REGIONAL_ENDPOINTS = regional
  AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  AWS_SDK_LOAD_CONFIG = 1
No CDK environment variables%

Description

data/resources schema:

 Conversation: a
            .model({
                playerCharacterRelationship: a.belongsTo(
                    "PlayerCharacterRelationship"
                ),
                messages: a.hasMany("Message"), //add conversation context
                lastMessage: a.hasOne("Message"),
                owner: a.string(),
            })
            .authorization([a.allow.owner()]),
        Message: a
            .model({
                conversation: a.belongsTo("Conversation"),
                sender: a.string().required(),
                content: a.string().required(),
                owner: a.string(),
            })
            .authorization([a.allow.owner()]),

addNewMessage():

export const addNewMessage = async (
    userId: string,
    content: string,
    senderId: string,
    conversation: Schema["Conversation"],
) => {
    const messageData: any = {
        conversation,
        sender: senderId,
        content,
        owner: userId,
    };
    //Create Message
    const { data: message, errors } = await dataClient.models.Message.create(
        messageData
    );
    //Update conversation with last message
    const {
        data: updatedConversation,
        errors: conversationErrors,
    } = await dataClient.models.Conversation.update({
        id: conversation.id,
        lastMessage: message,
    });
    //Check if messages added
    const {
        data: newMessages,
        errors: newErrors,
    } = await updatedConversation.messages();

    return message;
};

Using the above code, when I add a message to a conversation then check the conversation.messages list, I do not always see the newly added messages.

It seems very inconsistent, sometimes the messages are added and the other times they are missing. All checked via multiple logs.

Useful info:

Side note:

AakashKB commented 6 months ago

Further findings:

Functioning code in frontend that retrieves all messages without issue:

const {
        data,
        errors,
    } = await amplifyClient.models.PlayerCharacterRelationship.get(
        { id },
        {
            selectionSet: ["lastConversation.*", "lastConversation.messages.*"],
        }
    );

const messages = data?.lastConversation?.messages;
AakashKB commented 6 months ago

Updated lambda code to pull messages via selectionSet as opposed to using conversation.messages() function to make a new request, fixed the issue, messages are all pulled as expected.

Still keeping ticket open to track issue with conversation.messages(), my findings can be a workaround

ykethan commented 6 months ago

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

david-mcafee commented 6 months ago

@AakashKB I have not yet been able to repro this issue. Perhaps this is unrelated, but it looks like your function is returning the original message that was created, and not the retrieved messages - could you include a code snippet of where you are querying the parent / retrieving the child records, and how you are logging the output?

I was able to add both messages and lastMessage relationships, and they worked as expected. Can you potentially try adding further logging to your function to check the updates at each step (what conversation is being passed to the function, what is the output of each operation, etc). I'm wondering if the output you are seeing is from a previous update and/or query, and not the latest.

Lastly, does this only happen in Lambda?

For reference, here is my reproduction code:

    // Create parent record:
    const { data: conversation } = await client.models.Conversation.create({
      title: `${Date.now()}`,
    });

    // Create Message with parent record:
    const { data: message } = await client.models.Message.create({
      content: "First Message",
      conversation,
    });

    // Update conversation with last message
    const { data: updatedConversation } =
      await client.models.Conversation.update({
        id: conversation.id,
        lastMessage: message,
      });

    // Retrieve `hasMany` messages:
    const { data: newMessages } = await updatedConversation.messages();

    console.log("retrieve messages:", newMessages);

    // Retrieve last message:
    const { data: lastMessage } = await updatedConversation.lastMessage();

    console.log("retrieve last message:", lastMessage);

    // Create second Message, include parent record:
    const { data: secondMessage } = await client.models.Message.create({
      content: "Second Message",
      conversation,
    });

    // Update conversation with last message
    const { data: updatedConversation2 } =
      await client.models.Conversation.update({
        id: conversation.id,
        lastMessage: secondMessage,
      });

    // Retrieve `hasMany` messages after second update:
    const { data: newMessages2 } = await updatedConversation2.messages();

    console.log("retrieve messages after second update:", newMessages2);

    // Retrieve last message after second update:
    const { data: lastMessage2 } = await updatedConversation2.lastMessage();

    console.log("retrieve last message after second update:", lastMessage2);
AakashKB commented 6 months ago

@david-mcafee I returned the message but can confirm it works without, because when I refresh the page, it pulls the latest conversation, and all the messages are present.

I logged conversation, messages, etc. at every step of the function, and it consistently confirmed my issues.

If you look at my further findings comment above, it seems only difference is whether I directly pull via .messages() vs using a selectionSet to retrieve. Already ruled out lastMessage being an issue.

Still need to verify if only on Lambda, but it consistently does happen at lambda.

chrisbonifacio commented 6 months ago

Hi @AakashKB can you upgrade to the latest versions of @aws-amplify/backend and @aws-amplify/backend-cli

The code you shared is an older syntax and is no longer valid in the release version of Gen 2.

The updated syntax requires a reference id for each relationship. https://docs.amplify.aws/react/build-a-backend/data/data-modeling/relationships/

After upgrading, please let us know if the issue persists.

chrisbonifacio commented 5 months 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!

github-actions[bot] commented 5 months ago

This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please open a new issue that references this one.