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
amplify api add
Account
object in amplify/backend/api/app/schema.graphql
amplify codegen models
amplify api update
amplify mock
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
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?
@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.
@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.
@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 :)
This issue has been automatically closed because of inactivity. Please open a new issue if you are still encountering problems.
@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?
@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!
@undefobj sounds good :) Thanks for considering this as a feature request 馃憤
@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?
@undefobj Is there any update related to this feature?
@undefobj Is there any update related to this feature?
seconded, bump
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?
Most helpful comment
@undefobj Is there any update related to this feature?