Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock GraphQL API using DataStore doesn't work #322

Open
blydewright opened this issue May 3, 2020 · 20 comments
Open

Mock GraphQL API using DataStore doesn't work #322

blydewright opened this issue May 3, 2020 · 20 comments
Labels

Comments

@blydewright
Copy link

blydewright commented May 3, 2020

Note: If your issue/bug is regarding the AWS Amplify Console service, please log it in the
Amplify Console GitHub Issue Tracker

Describe the bug
All operations (saving, syncing etc.) between my client (ReactJS app) and the mock API fail with various reasons. Whereas running the same exact API on cloud stack (after amplify push) works 100%.

My simple schema @model:

type Account
@model
{
    id: ID!
    givenName: String!
    familyName: String!
    email: String!
}

That generates the following GraphQL schema:

type Account {
  id: ID!
  givenName: String!
  familyName: String!
  email: String!
  _version: Int!
  _deleted: Boolean
  _lastChangedAt: AWSTimestamp!
}

enum ModelSortDirection {
  ASC
  DESC
}

type ModelAccountConnection {
  items: [Account]
  nextToken: String
  startedAt: AWSTimestamp
}

input ModelStringInput {
  ne: String
  eq: String
  le: String
  lt: String
  ge: String
  gt: String
  contains: String
  notContains: String
  between: [String]
  beginsWith: String
  attributeExists: Boolean
  attributeType: ModelAttributeTypes
  size: ModelSizeInput
}

input ModelIDInput {
  ne: ID
  eq: ID
  le: ID
  lt: ID
  ge: ID
  gt: ID
  contains: ID
  notContains: ID
  between: [ID]
  beginsWith: ID
  attributeExists: Boolean
  attributeType: ModelAttributeTypes
  size: ModelSizeInput
}

input ModelIntInput {
  ne: Int
  eq: Int
  le: Int
  lt: Int
  ge: Int
  gt: Int
  between: [Int]
  attributeExists: Boolean
  attributeType: ModelAttributeTypes
}

input ModelFloatInput {
  ne: Float
  eq: Float
  le: Float
  lt: Float
  ge: Float
  gt: Float
  between: [Float]
  attributeExists: Boolean
  attributeType: ModelAttributeTypes
}

input ModelBooleanInput {
  ne: Boolean
  eq: Boolean
  attributeExists: Boolean
  attributeType: ModelAttributeTypes
}

input ModelSizeInput {
  ne: Int
  eq: Int
  le: Int
  lt: Int
  ge: Int
  gt: Int
  between: [Int]
}

input ModelAccountFilterInput {
  id: ModelIDInput
  givenName: ModelStringInput
  familyName: ModelStringInput
  email: ModelStringInput
  and: [ModelAccountFilterInput]
  or: [ModelAccountFilterInput]
  not: ModelAccountFilterInput
}

enum ModelAttributeTypes {
  binary
  binarySet
  bool
  list
  map
  number
  numberSet
  string
  stringSet
  _null
}

type Query {
  syncAccounts(filter: ModelAccountFilterInput, limit: Int, nextToken: String, lastSync: AWSTimestamp): ModelAccountConnection
  getAccount(id: ID!): Account
  listAccounts(filter: ModelAccountFilterInput, limit: Int, nextToken: String): ModelAccountConnection
}

input CreateAccountInput {
  id: ID
  givenName: String!
  familyName: String!
  email: String!
  _version: Int
}

input UpdateAccountInput {
  id: ID!
  givenName: String
  familyName: String
  email: String
  _version: Int
}

input DeleteAccountInput {
  id: ID
  _version: Int
}

type Mutation {
  createAccount(input: CreateAccountInput!, condition: ModelAccountConditionInput): Account
  updateAccount(input: UpdateAccountInput!, condition: ModelAccountConditionInput): Account
  deleteAccount(input: DeleteAccountInput!, condition: ModelAccountConditionInput): Account
}

input ModelAccountConditionInput {
  givenName: ModelStringInput
  familyName: ModelStringInput
  email: ModelStringInput
  and: [ModelAccountConditionInput]
  or: [ModelAccountConditionInput]
  not: ModelAccountConditionInput
}

type Subscription {
  onCreateAccount: Account @aws_subscribe(mutations: ["createAccount"])
  onUpdateAccount: Account @aws_subscribe(mutations: ["updateAccount"])
  onDeleteAccount: Account @aws_subscribe(mutations: ["deleteAccount"])
}

Bootstrap DataStore hack (as first DataStore.save() operation was never syncing with cloud - suggested in another thread):

useEffect(() => {
        let subscription = null
        let cancel = false

        const init = async () => {
            subscription = DataStore.observe().subscribe(console.log('Hack for DataStore init.'))
        }

        if (!cancel) {
            init()
        }

        return () => {
            cancel = true

            if (subscription) {
                subscription.unsubscribe()
            }
        }
    }, [])

An instance is created through a click handler, and a subscription is created at that point too:

const account = await DataStore.save(
                new Account({
                    email: "help.me@thanks",
                    familyName: "Doe",
                    givenName: "John",
                })
            )

const accountSubscription = DataStore.observe(Account, account.id).subscribe(msg => {
                console.log(msg.model, msg.opType, msg.element)
            })

The DataStore.save() operation correctly creates and saves the Account locally (in IndexedDb), but returns the following error when trying to sync with Mock API:

{data: {createAccount: null},…}
data: {createAccount: null}
createAccount: null
errors: [{message: "Cannot return null for non-nullable field Account._version.",…}]
0: {message: "Cannot return null for non-nullable field Account._version.",…}
locations: [{line: 7, column: 5}]
message: "Cannot return null for non-nullable field Account._version."
path: ["createAccount", "_version"]
0: "createAccount"
1: "_version"

And further to this, it tries to begin syncing with GraphQL requests, which returns:

{data: {syncAccounts: null},…}
data: {syncAccounts: null}
syncAccounts: null
errors: [{message: "Unknown operation name: Sync", errorType: null, data: null, errorInfo: null,…}]
0: {message: "Unknown operation name: Sync", errorType: null, data: null, errorInfo: null,…}
data: null
errorInfo: null
errorType: null
locations: [{line: 2, column: 3, sourceName: "GraphQL request"}]
0: {line: 2, column: 3, sourceName: "GraphQL request"}
column: 3
line: 2
sourceName: "GraphQL request"
message: "Unknown operation name: Sync"
path: ["syncAccounts"]
0: "syncAccounts"

Amplify CLI Version
4.18.1

To Reproduce

  • Create an API through amplify api add
  • Create the Account object in amplify/backend/api/app/schema.graphql
  • Run amplify codegen models
  • Run amplify api update
    -- Api key authentication
    -- Conflict detection = Optimistic concurrency
    -- Enable DataStore for entire API
  • Run amplify mock
  • Open ReactJS app and observe console/request errors

Expected behavior
The Creating and syncing should produce no errors, as is the case when amplify push to cloud and running the app against a "live" API. Mock environment should work in the same way, and is needed to speed up dev!

Desktop (please complete the following information):
Windows 10 Pro build 19041 running WSL2 Ubuntu
This stack is setup on Ubuntu - 18.04

NodeJS version: v13.13.0

@blydewright
Copy link
Author

blydewright commented May 3, 2020

Further investigation revealed that adding @auth directive to my model allowed the objects to be created in the Mock API GraphQL database successfully, however the create operation still returns the error.

message: "Cannot return null for non-nullable field Account._version.", operation: "Create"

My updated model declaration:

type Account
@model
@auth(rules: [
    {allow: owner}
])
{
    id: ID!
    givenName: String!
    familyName: String!
    email: String!
}

It seems like the version resolver is not working in a mock environment, and therefore breaking the synchronization flow?

@yuth
Copy link
Contributor

yuth commented May 4, 2020

@blydewright Amplify Mock does not have support for sync resolvers. To test DataStore syncing, the API will have to be deployed to AppSync.

Is there any specific reason why you would like to run the DataStore using mock? DataStore should work locally without any servers when sync is not enabled.

@yuth yuth added DataStore question Further information is requested pending-close-response-required labels May 4, 2020
@arnm
Copy link

arnm commented May 5, 2020

@blydewright Amplify Mock does not have support for sync resolvers. To test DataStore syncing, the API will have to be deployed to AppSync.

Is there any specific reason why you would like to run the DataStore using mock? DataStore should work locally without any servers when sync is not enabled.

For me, it is a much better development experience to use Amplify mock. It enables quicker iteration during development. Especially, if you are making many changes to the schema. Amplify push takes long enough that it can be slightly annoying when making many changes or trying out new things.

Another thing I noticed was that Amplify mock forces you to supply all the values in a model. Otherwise it will throw an error like the following:

Error: Field email should be of type string, undefined received

With an AppSync deployment it do not get this error when not providing optional fields in a model.

@blydewright
Copy link
Author

@yuth as @arnm pointed out above, the development experience and speed in using Mock is the primary advantage. I myself wanted to test flows of using sync locally before pushing to cloud, as I needed to update my schema many many times, and manually deleting the mock sqlite database when I wanted to re-initialise locally, was far easier then destroying an entire environment and re-deploying it again from scratch as I had to do multiple times whilst learning about limitations in modifying keys/relationships between models during deploys.

As AppSync doesn't support mock environment right now, perhaps it can be discussed as a potential improvement? Is the lack of AppSync/mock environment support documented anywhere? I found the lack of substantial (up-to-date) documentation to be my biggest hurdle in learning and making progress... otherwise I'm quite excited to be using Amplify :)

@stale
Copy link

stale bot commented May 13, 2020

This issue has been automatically closed because of inactivity. Please open a new issue if you are still encountering problems.

@undefobj
Copy link
Contributor

@blydewright @arnm We can make this a feature request for mock, however the key is that the Conflict Resolution will not be locally mockable. For instance Auto Merge logic. Is something very simplistic with mock (accept all writes even on conflict, or alternatively reject on conflict) ok for you?

@arnm
Copy link

arnm commented May 14, 2020

@undefobj Yes, that is understandable. If we can define a simple strategy for local mocking, wether it be cached/offline values overwrite any conflicts in mock db or the other way around, works fine with me.

Please reference that new issue here for visibility! Thanks!

@blydewright
Copy link
Author

@undefobj sounds good :) Thanks for considering this as a feature request 👍

@SuperManfred
Copy link

SuperManfred commented May 17, 2020

@blydewright @arnm We can make this a feature request for mock, however the key is that the Conflict Resolution will not be locally mockable. For instance Auto Merge logic. Is something very simplistic with mock (accept all writes even on conflict, or alternatively reject on conflict) ok for you?

I was wondering would if it be possible for local mock env to create a local table and also an additional table that pretended to be the cloudDB (but also running on local machine), as this might let us keep Conflict Resolution. @undefobj would that be better than disabling Conflict Resolution?

@wagneramaral
Copy link

@undefobj Is there any update related to this feature?

@amlcodes
Copy link

amlcodes commented Dec 8, 2020

@undefobj Is there any update related to this feature?

seconded, bump

@amp-aarden
Copy link

amp-aarden commented Dec 9, 2020

I'm having this same error, only with an API error message that's returned from the mock API with a message of "Cannot return null for non-nullable field... _version". It sounds like this may be the same issue as described above. Note that the API is the only thing I'm running in local mock mode. All else (e.g. Auth, Functions, ....) are all running in AWS cloud.

Note also that I do NOT get this error if when doing amplify update api or amplify add api I choose No when asked "Configure conflict detection?" Choosing any other option will produce this error in local mock mode. So, not having conflict detection (in development mode) does provide a workaround... UNLESS you need to use any update queries... which will NOT be generated by amplify codegen unless conflict detection is configured!! This appears to be an egregious oversite!!!

Despite this (sort of) workaround for the _version (which I have no apparent control over), using @connect causes similar errors to be thrown for any nested data as in the following code:

type MyUser
  @model
  @auth(
    rules: [
      { allow: public, provider: apiKey }, 
      { allow: private, provider: iam }, 
      { allow: owner },
    ]
  )
  @key(fields: ["id"])
{
  id: ID!
  email: AWSEmail!
  username: String
  firstName: String
  lastName: String
  profilePicThumbnail: Image @connection
}

type Image
  @model 
  @auth(
    rules: [
      { allow: public, provider: apiKey }, 
      { allow: private, provider: iam }, 
      { allow: owner },
    ]
  )
  @key(fields: ["id"])
{
  id: ID!
  title: String
  description: String
  isThumbnail: Boolean!
}

profilePicThumbnail throws "Cannot return null for non-nullable field" errors when no data is provided in the database, even though it's not a required field! This is VERY problematic! Is there a workaround for this?

@marlonmarcello
Copy link

marlonmarcello commented Apr 8, 2021

I just bumped into this same issue here with _version. I can't run Mutations at the moment.
Even if you remove _version from the Query and Mutation you get the same error with _lastChangedAt.

@loganpowell
Copy link

Note also that I do NOT get this error if when doing amplify update api or amplify add api I choose No when asked "Configure conflict detection?"

Thank you for sharing this. It seems that mocking isn't fully supported for DataStore yet. I'll keep my 👀 on this issue.

@josephpenafiel
Copy link

Hello guys. I'm stuck with this issue too.
Any recommendations?

@thecodehen
Copy link

Sometimes if I don't mock the API, I could connect to the online backend on AWS (even though I was testing locally using npm run start). Perhaps this can help with the issue of DataStore not mocking locally (create a new Amplify backend environment with test data).

However, I could not consistently connect to the online backend while testing locally (sometimes it would just not find the local mock server and return no data). Anyone has advice connecting a local instance to an online backend?

@josefaidt josefaidt added the p3 label Feb 22, 2022
@yano3nora
Copy link

#104

@alharris-at alharris-at transferred this issue from aws-amplify/amplify-cli May 17, 2022
@onelittleant
Copy link

The problem here is that the mock database does not automatically populate _version and _lastChangedAt. These two fields:

  1. Exist automatically on every model.
  2. Are non-nullable on every model.
  3. Are automatically populated when records are created in PRODUCTION, but they are set to null when records are created in MOCK.
  4. The amplify auto-generated queries and mutations request that ALL fields on a model be returned by default, including _version and _lastChangedAt
  5. Because these two fields are always null in the mock environment, the auto-generated queries and mutations will fail to complete with: "Cannot return null for non-nullable field MODEL_NAME._version" and/or "Cannot return null for non-nullable field MODEL_NAME._version"

I can confirm this behavior in amplify cli version 10.5.2

@nathankopp
Copy link

nathankopp commented Jan 10, 2023

How is this still broken? The entire "amplify mock" feature is completely useless for any real-world app if it doesn't work with apps that use the conflict detection feature.

Note that there seems to be a similar error related to the "owner" field not being filled in when using AMAZON_COGNITO_USER_POOLS with the mock environment. :-(

Using version 10.6.1.

This might be a showstopper for my long-term adoption of Amplify as serverless development environment. Deployment to the cloud (via "amplify push") is painfully slow if there are any schema changes, so the edit-deploy-test roundtrip really destroys developer productivity waiting on the "deploy" phase. I was hoping that local mocking would reduce the need to use "amplify push" to test every change, but it needs to work for complex real-world apps, including apps that use complex authentication and conflict detection.

@nelsonmora48
Copy link

Waiting for a Bug Fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests