amplify-category-api icon indicating copy to clipboard operation
amplify-category-api copied to clipboard

Api `hasMany` relationships are inconsistently retrieved

Open AakashKB opened this issue 10 months ago • 6 comments

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:

  • The messages are consistently added when I manually check DynamoDB, it seems the issue then is in the retrieval
  • This code is run in a lambda function, lambda has full permissions to API at the moment
  • Curious if it is cause by adding the lastMessage property to conversation with a hasOne relationship, maybe there is confusion on lastMessage vs other messages?
  • No errors output or thrown by api
  • Every time I try to retrieve messages from the conversation, it is always the same subset retrieved, and the same subset left out.

Side note:

  • This would all be a lot faster to debug and test if lambda logs were outputted on console similar to gen1 mock lambdas. It is currently annoying to check cloudwatch each time.

AakashKB avatar Apr 13 '24 21:04 AakashKB

Further findings:

  • Removed lastMessage property, issue still persists so ruling it out as issue
  • When I pull conversation messages in front end via selectionSet, this issue does not happen, it only seems to be happening in lambda (however my lambda code retrieves via the conversation.messages() function via a new request as opposed to defining in selectionSet, testing this next.

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 avatar Apr 13 '24 22:04 AakashKB

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

AakashKB avatar Apr 13 '24 23:04 AakashKB

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

ykethan avatar Apr 15 '24 15:04 ykethan

@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);

david-mcafee avatar Apr 15 '24 22:04 david-mcafee

@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.

AakashKB avatar Apr 25 '24 16:04 AakashKB

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 avatar May 07 '24 18:05 chrisbonifacio

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 avatar May 29 '24 20:05 chrisbonifacio

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.

github-actions[bot] avatar May 29 '24 20:05 github-actions[bot]