Closed maxfahl closed 2 years ago
Hi @maxfahl 👋 thank you for raising this issue. Are you importing and using models generated by the CLI command amplify codegen models
? You shared the graphql mutation type definition, which means you have a graphql
folder, but do you have a models
folder?
We'll also want to be certain you'd enabled DataStore for your API, which you can check by looking for this setting in your amplify/backend/api/starter/cli-inputs.json
file
...
"conflictResolution": {
"defaultResolutionStrategy": {
"type": "AUTOMERGE"
}
OR by running amplify update api
and if you see an option for Disable conflict detection
then that means DataStore is already enabled. Otherwise, make sure to enable it.
Sorry for for the delayed response.
Are you importing and using models generated by the CLI command
amplify codegen models
?
Yes I am.
You shared the graphql mutation type definition, which means you have a
graphql
folder, but do you have amodels
folder?
I have run amplify codegen
, amplify codegen models
(isn't codegen doing models as well?) as well as amplify api gql-compile
.
...which you can check by looking for this setting in your
amplify/backend/api/starter/cli-inputs.json
file
I had disabled conflict resolutions all together when I posted the issue, so datastore was disabled. I enabled it again and am now using the OPTIMISTIC_CONCURRENCY
strategy. After that I ran all the codegen commands I listed above, but no difference. Still getting the The source object is not a valid model
error. I've recompiled my react code as well, but it doesn't want to agree that it's an actual model. Also, I disabled conflict resolutions because when they're enabled, it won't delete record directly, it just flags them as deleted, and I could figure out how to tell the API to delete them directly. Also, I don't think there's a way to filter items based on the deleted flag, is there? I have lambdas hooked up for when I delete an entry, so that related entries are deleted as well, and I want them to run concurrently, so to speak. The change of deleted: true
won't trigger these I presume?
For now, I will disable conflict resolution so that I can continue working, but please let me know if there's something else I can try, and I will gladly give it a shot.
For now I'm using the produce
function from the immer
library to mutate the object, but I have to do cleanup myself based on the model, such as deleting the createdAt
and updatedAt
properties.
I am having the same/similar issue. I have a React Context with a useEffect() to start the query/subscription and providing an update function as well as the query results in the ContextProvider.
import React, { createContext, useEffect } from "react";
import { DataStore, Predicates } from 'aws-amplify';
import { Context } from './models';
export const ContextContext = createContext();
export const ContextContextProvider = ({ children }) => {
const [context, setContext] = React.useState({});
useEffect(() => {
const subscription = DataStore.observeQuery(
Context
).subscribe(snapshot => {
const { items, isSynced } = snapshot;
console.log(`[Snapshot] item count: ${items.length}, isSynced: ${isSynced}`);
setContext(items.length ? { ...items[0] } : {});
});
return () => subscription.unsubscribe();
}, []);
...
const updateContext = async (data) => {
console.log(context);
DataStore.save(Context.copyOf(context, updated => {
updated.field_to_change = data.field_to_change;
console.log(updated);
}));
return (
<ContextContext.Provider value={{ context, updateContext }}>
{children}
</ContextContext.Provider>
);
};
The console output for both context and updated are identical except for the modified data, but I get the same error. DataStore is configured for automerge.
@chow11 where are you running into this issue? When querying or when updating the record? Are you sure that you're passing a valid model instance to Context.copyOf
and not the empty object from the initial state? It looks like setContext(items.length ? { ...items[0] } : {});
is spreading the attributes of a model instance to a normal object. I suspect the issue stems from here in your case.
DataStore only works with valid instances of the models initialized with the schema (by using initSchema
, which you can see in the models/index.js
file.
@maxfahl
For now, I will disable conflict resolution so that I can continue working, but please let me know if there's something else I can try, and I will gladly give it a shot.
I would've asked to look at how you might've been getting editedMemory
in your code snippet, which is being passed to Memory.copyOf
. That might help me see why it's not considered a valid model by DataStore. Could be something similar to what I noticed about @chow11 's code.
https://docs.amplify.aws/lib/datastore/data-access/q/platform/js/#create-and-update
Besides that, unless you have a specific use case and/or requirement for offline capabilities, I think it would be a smoother dev experience sticking to API.graphql
without DataStore.
Also, I don't think there's a way to filter items based on the deleted flag, is there?
Sounds like you might be querying with API.graphql
here? The "soft delete" behavior is a necessity in DataStore-enabled APIs because other clients have to be able to sync which records should be removed from their local database.
https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-delta-sync.html
The _deleted field on Post is used for DELETE operations. When clients are offline and records are removed from the Base table, this attribute notifies clients performing synchronization to evict items from their local cache. In cases where clients are offline for longer periods of time and the item has been removed before the client can retrieve this value with a Delta Sync query, the global catch-up event in the base query (configurable in the client) runs and removes the item from the cache. This field is marked optional because it only returns a value when running a sync query that has deleted items present.
removing the spread of the query results when copying to the local state resolved this error. Thank you very much for that tip.
I would've asked to look at how you might've been getting
editedMemory
in your code snippet
I'm using react query
together with query code generated by amplify generate
, like this:
import { deleteMemory, listMemories } from '../api/memory-api'
const {
isLoading: isMemoriesLoading,
data: memories,
error: memoriesFetchError,
} = useQuery(
MEMORY_LIST_CACHE_KEY,
useCallback(() => listMemories(family.id), [family]),
{
enabled: !!family,
}
)
listMemories
looks like this:
import { listMemories as listMemoriesQuery } from '../graphql/queries'
export const listMemories = async (familyId: string | undefined) => {
return (
await runQuery<ListMemoriesQuery>(listMemoriesQuery, {
filter: {
familyID: {
eq: familyId,
},
} as ModelMemoryFilterInput,
})
).data?.listMemories?.items as Memory[]
}
and finally, the listMemories query, generated by amplify generate
:
export const listMemories = /* GraphQL */ `
query ListMemories($filter: ModelMemoryFilterInput, $limit: Int, $nextToken: String) {
listMemories(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
title
description
owner
media {
items {
id
key
path
mime
title
description
owner
memoryID
createdAt
updatedAt
}
nextToken
}
familyID
createdAt
updatedAt
}
nextToken
}
}
`
One of the memories returned by the query is then passed as a prop to a form that does the actual update. I was first thinking maybe react query
did something to the object returned by the API. I removed react query
from the equation, but the error was still present.
As you say, I don't have any specific use case and/or requirement, neither do I need offline capabilities, so maybe I should just skip datastore alltogether and through that disable conflict resolutions for now. Would just be handy to be able to use the Model.copyOf() helper.
Could you please be a little bit more specific on what I miss out on not using datastore?
@maxfahl hmm, if you were using the memory record received from GraphQL that would not be a Memory instance, just regular json data. The record you're trying to update has to be queried by DataStore to get a Model instance back.
here's what I mean
In the DataStore query result, the records are Model
instances, which can be passed to Model.copyOf
. The GraphQL results are not.
Could you please be a little bit more specific on what I miss out on not using datastore?
The main benefit of DataStore is the offline capability so if you don't need that, you're not missing out on much.
I think the only drawback with API.graphql is the typing suggestions since you mentioned the helper, which I assume you were using for the field suggestions? For that you'd have to be using TypeScript. If you can use TS in your project, you can configure codegen to generate a file with the type declarations for the schema by running amplify configure codegen
and selecting typescript
for the "Choose the code generation language target" prompt. Then run amplify codegen
to generate an API.ts
file.
@chrisbonifacio thank you for taking the time to help me, really appreciate it. You can close the issue now.
@chrisbonifacio I'm am having this issue but the above don't appear to be of help. Was trying to follow data store documentation
const user = await DataStore.query(User, (u) => u.email('eq', username.VALUE));
await DataStore.save(
User.copyOf(user, (updated) => {
(updated.otp = otpNew), (updated.otp_expiration = otpExpirationNew);
})
);
I get
[ERROR] 07:45.505 DataStore - The source object is not a valid model, Object {
"source": Array [
User {
"_deleted": null,
"_lastChangedAt": 1657335450543,
"_version": 1,
"createdAt": "2022-07-09T02:57:30.498Z",
"date_of_birth": "01-01-1990",
"email": "john@gmail.com",
"first_name": "John",
"id": "ace1cbea-15e6-4c77-8f31-666d06818faa",
"last_name": "Doe",
"otp": 123456,
"otp_expiration": "08-01-2022",
"updatedAt": "2022-07-09T02:57:30.498Z",
},
],
}
type User @model @auth(rules: [{ allow: owner }]) {
id: ID!
first_name: String
last_name: String
email: String
date_of_birth: String
otp: Int
otp_expiration: String
}
copyOf needs a single instance of the model, not an array. For example..
DataStore.save( User.copyOf(user[0], (updated) => {
copyOf needs a single instance of the model, not an array. For example..
DataStore.save( User.copyOf(user[0], (updated) => {
@bc4253 Thank you!
This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.
Looking for a help forum? We recommend joining the Amplify Community Discord server amplify-help
forum.
Before opening, please confirm:
JavaScript Framework
React
Amplify APIs
Authentication, GraphQL API, Storage
Amplify Categories
No response
Environment information
Describe the bug
When trying create copy of a model instance through
Model.copyOf()
, I get the errorThe source object is not a valid model
.The original object comes through a query generated by the amplify-cli..
The model:
The query definition:
And lastly the typescript function I pass to
await API.graphql()
:These are all generated through
amplify api gql-compile
andamplify codegen
.The root of the problem seems to be the check in
isValidModelConstructor()
atdatastore.ts:119
. wheremodelNamespaceMap.has(obj)
returns false. I havent mutated the object in any way before this. Shouldnt my model be in this map?I try to make a copy like this:
I originally get the Memory via
ListMemoriesQuery
also generated by amplify.Expected behavior
to return a clone of the instance with new values.
Reproduction steps
I will try and put together a minimal sandbox example, but my time is extremely short at the moment. I wanted to post an issue with as much information as possible so that you possible could see what the issue might be. I'm sorry for this, you can delete this report if it is not enough.
Code Snippet
No response
Log output
aws-exports.js
const awsmobile = { "aws_project_region": "eu-north-1", "aws_cognito_identity_pool_id": "eu-north-1:17206967-ad7c-483a-88f2-6844a73f57a8", "aws_cognito_region": "eu-north-1", "aws_user_pools_id": "eu-north-1_amEMPFTZw", "aws_user_pools_web_client_id": "44bn51v6ts67f9mbnpn78nqs85", "oauth": {}, "aws_cognito_username_attributes": [ "EMAIL" ], "aws_cognito_social_providers": [], "aws_cognito_signup_attributes": [ "EMAIL" ], "aws_cognito_mfa_configuration": "OFF", "aws_cognito_mfa_types": [ "SMS" ], "aws_cognito_password_protection_settings": { "passwordPolicyMinLength": 8, "passwordPolicyCharacters": [] }, "aws_cognito_verification_mechanisms": [ "EMAIL" ], "aws_user_files_s3_bucket": "family-media102846-dev", "aws_user_files_s3_bucket_region": "eu-north-1", "aws_appsync_graphqlEndpoint": "https://pxc2ujiqsfc7boe7uo2usy6zvu.appsync-api.eu-north-1.amazonaws.com/graphql", "aws_appsync_region": "eu-north-1", "aws_appsync_authenticationType": "API_KEY", "aws_appsync_apiKey": "X" };
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