DataStore.save warning: "Variable 'input' has coerced Null value for NonNull type 'String!'"
Before opening, please confirm:
- [X] I have searched for duplicate or closed issues and discussions.
- [X] I have read the guide for submitting bug reports.
- [X] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
Angular
Amplify APIs
DataStore
Amplify Categories
api
Environment information
System:
OS: macOS 13.5.2
CPU: (10) arm64 Apple M1 Pro
Memory: 77.30 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 18.13.0 - /usr/local/bin/node
npm: 9.8.1 - /opt/homebrew/bin/npm
Browsers:
Chrome: 116.0.5845.187
Safari: 16.6
npmPackages:
@angular-devkit/build-angular: ^15.0.0 => 15.2.9
@angular/animations: ~15.0.0 => 15.0.4
@angular/cdk: ~15.0.0 => 15.0.4
@angular/cli: ~15.0.0 => 15.0.5
@angular/common: ~15.0.0 => 15.0.4
@angular/compiler: ~15.0.0 => 15.0.4
@angular/compiler-cli: ~15.0.0 => 15.0.4
@angular/core: ~15.0.0 => 15.0.4
@angular/forms: ~15.0.0 => 15.0.4
@angular/material: ~15.0.0 => 15.0.4
@angular/platform-browser: ~15.0.0 => 15.0.4
@angular/platform-browser-dynamic: ~15.0.0 => 15.0.4
@angular/router: ~15.0.0 => 15.0.4
@angular/service-worker: ~15.0.0 => 15.0.4
@dicebear/collection: ^6.0.4 => 6.0.4
@dicebear/core: ^6.0.4 => 6.0.4
@ffmpeg/core: ^0.11.0 => 0.11.0
@ffmpeg/ffmpeg: ^0.11.6 => 0.11.6
@stripe/stripe-js: ^1.35.0 => 1.54.2
@types/file-saver: ^2.0.5 => 2.0.5
@types/jasmine: ~3.10.0 => 3.10.13
@types/node: ^12.11.1 => 12.20.55 (18.17.17)
aws-amplify: ^5.3.11 => 5.3.11
chart.js: ^4.4.0 => 4.4.0
chart.js-auto: undefined ()
chart.js-helpers: undefined ()
docx: ^7.7.0 => 7.8.2
ini: ^1.3.5 => 1.3.8 (3.0.1)
inquirer: ^6.5.1 => 6.5.2 (8.2.4)
jasmine-core: ~4.0.0 => 4.0.1 (3.99.1)
karma: ~6.3.0 => 6.3.20
karma-chrome-launcher: ~3.1.0 => 3.1.1
karma-coverage: ~2.1.0 => 2.1.1
karma-coverage-coffee-example: 1.0.0
karma-jasmine: ~4.0.0 => 4.0.2
karma-jasmine-html-reporter: ~1.7.0 => 1.7.0
ngx-filesaver: ^14.0.0 => 14.0.0
node-html-parser: ^6.1.4 => 6.1.10
rxjs: ~7.5.0 => 7.5.7 (6.6.7, 7.8.1)
rxjs/ajax: undefined ()
rxjs/fetch: undefined ()
rxjs/internal-compatibility: undefined ()
rxjs/operators: undefined ()
rxjs/testing: undefined ()
rxjs/webSocket: undefined ()
tslib: ^2.4.1 => 2.6.2 (1.14.1, 2.5.0)
typescript: ~4.8.4 => 4.8.4
webpack-cli: ^4.10.0 => 4.10.0
zone-mix: undefined ()
zone-node: undefined ()
zone-testing: undefined ()
zone.js: ~0.11.4 => 0.11.8
zone.js/async-stack-tagging: undefined ()
zone.js/async-stack-tagging.min: undefined ()
zone.js/async-test: undefined ()
zone.js/async-test.min: undefined ()
zone.js/fake-async-test: undefined ()
zone.js/fake-async-test.min: undefined ()
zone.js/jasmine-patch: undefined ()
zone.js/jasmine-patch.min: undefined ()
zone.js/long-stack-trace-zone: undefined ()
zone.js/long-stack-trace-zone.min: undefined ()
zone.js/mocha-patch: undefined ()
zone.js/mocha-patch.min: undefined ()
zone.js/proxy: undefined ()
zone.js/proxy.min: undefined ()
zone.js/sync-test: undefined ()
zone.js/sync-test.min: undefined ()
zone.js/task-tracking: undefined ()
zone.js/task-tracking.min: undefined ()
zone.js/webapis-media-query: undefined ()
zone.js/webapis-media-query.min: undefined ()
zone.js/webapis-notification: undefined ()
zone.js/webapis-notification.min: undefined ()
zone.js/webapis-rtc-peer-connection: undefined ()
zone.js/webapis-rtc-peer-connection.min: undefined ()
zone.js/webapis-shadydom: undefined ()
zone.js/webapis-shadydom.min: undefined ()
zone.js/wtf: undefined ()
zone.js/wtf.min: undefined ()
zone.js/zone-bluebird: undefined ()
zone.js/zone-bluebird.min: undefined ()
zone.js/zone-error: undefined ()
zone.js/zone-error.min: undefined ()
zone.js/zone-legacy: undefined ()
zone.js/zone-legacy.min: undefined ()
zone.js/zone-patch-canvas: undefined ()
zone.js/zone-patch-canvas.min: undefined ()
zone.js/zone-patch-cordova: undefined ()
zone.js/zone-patch-cordova.min: undefined ()
zone.js/zone-patch-electron: undefined ()
zone.js/zone-patch-electron.min: undefined ()
zone.js/zone-patch-fetch: undefined ()
zone.js/zone-patch-fetch.min: undefined ()
zone.js/zone-patch-jsonp: undefined ()
zone.js/zone-patch-jsonp.min: undefined ()
zone.js/zone-patch-message-port: undefined ()
zone.js/zone-patch-message-port.min: undefined ()
zone.js/zone-patch-promise-test: undefined ()
zone.js/zone-patch-promise-test.min: undefined ()
zone.js/zone-patch-resize-observer: undefined ()
zone.js/zone-patch-resize-observer.min: undefined ()
zone.js/zone-patch-rxjs: undefined ()
zone.js/zone-patch-rxjs-fake-async: undefined ()
zone.js/zone-patch-rxjs-fake-async.min: undefined ()
zone.js/zone-patch-rxjs.min: undefined ()
zone.js/zone-patch-socket-io: undefined ()
zone.js/zone-patch-socket-io.min: undefined ()
zone.js/zone-patch-user-media: undefined ()
zone.js/zone-patch-user-media.min: undefined ()
npmGlobalPackages:
create-react-app: 5.0.1
firebase-tools: 12.5.4
gatsby-cli: 5.3.1
nativescript: 8.5.3
neovim: 4.10.1
npm: 9.8.1
typewritten: 1.5.1
vscode-langservers-extracted: 4.6.0
Describe the bug
I'm getting this warning when saving a Datastore object, after using obj.copyOf() to update one value on the object. I don't know when, but it stopped working some time ago, and I haven't been able to find a workaround or a fix, so this exact code has worked earlier. And what I think the weird thing is, is the fact that the "newRecording" changes from when I console.log it to when I save it in Datastore.
Expected behavior
The object should be updated accordingly and saved to Datastore without warning.
Reproduction steps
- Create a js web app
- Create a Datastore object and update it with obj.copyOf()
- Save it to Datastore
Code Snippet
const recording = new Recording({
name: title,
description: desc,
date: new Date().toISOString(),
fileName: file.name,
fileKey: '',
speakerCount: speakerCount,
languageCode: languageCode
});
...
const key = 'recordings/' + recording.id + '.' + fileType;
const newRecording = Recording.copyOf(recording, copy => {
copy.fileKey = key
});
try {
console.log(newRecording);
await DataStore.save(newRecording);
...
Log output
tslib:71 [WARN]
58:16.393 DataStore {recoverySuggestion: 'Ensure app code is up to date, auth directives exi… https://github.com/aws-amplify/amplify-js/issues',
localModel:
date: undefined
description: undefined
fileKey: "recordings/48cced19-cf0c-49aa-86a2-0c9a448a1343.mp3"
fileName: undefined
fileUrl: undefined
id: "48cced19-cf0c-49aa-86a2-0c9a448a1343"
interviewers: undefined
labels: undefined
languageCode: undefined
name: undefined
speakerCount: undefined
_version: undefined
message: "Variable 'input' has coerced Null value for NonNull type 'String!'",
operation: 'Create',
errorType: 'BadRecord',
…}
cause: locations: [{…}]
message: "Variable 'input' has coerced Null value for NonNull type 'String!'"
path: null
[[Prototype]]: Object
errorInfo: undefined
errorType: "BadRecord"
localModel: {id: '03d04c19-0b4d-4053-a553-cdc6fe36cbc6', name: undefined, date: undefined, description: undefined, fileKey: 'recordings/03d04c19-0b4d-4053-a553-cdc6fe36cbc6.mp3', …}
message: "Variable 'input' has coerced Null value for NonNull type 'String!'"
operation: "Create"process: "mutate"
recoverySuggestion: "Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues"
remoteModel: null
[[Prototype]]: Object
This is the log from printing the "newRecording" before saving it:
createdAt: null
date: "2023-09-16T15:28:04.732Z"
description: "Purchase tracking"
fileKey: "recordings/48cced19-cf0c-49aa-86a2-0c9a448a1343.mp3"
fileName: "Proshop Test.mp3"
fileUrl: null
id: "48cced19-cf0c-49aa-86a2-0c9a448a1343"
interviewers: null
labels: null
languageCode: "da-DK"
name: "Purchase test5"
speakerCount: 2
updatedAt: null
_deleted: undefined
_lastChangedAt: undefined
_version: undefined
aws-exports.js
No response
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
No response
Hello, @JoakimMellonn and thanks for creating this issue. Are you performing a save of the new Recording in that first line of example code? Can you also share your Amplify GraphQL schema? Thanks.
Hello, @JoakimMellonn and thanks for creating this issue. Are you performing a save of the
new Recordingin that first line of example code? Can you also share your Amplify GraphQL schema? Thanks.
I don't save the one called "recording" in Datastore, I just create it to get an ID and then copy it to put in the fileKey. I have found a workaround by saving it in Datastore before making the copy, but I don't see why this should be necessary.
The GraphQL schema for the Recording type is here (if more is needed please do let me know):
type Recording @model @auth(rules: [{allow: owner}]) {
id: ID!
name: String!
date: AWSDateTime
description: String
fileKey: String
fileName: String
fileUrl: String
speakerCount: Int!
languageCode: String
versions: [Version] @connection(keyName: "byRecording", fields: ["id"])
interviewers: [String]
labels: [String]
}
@JoakimMellonn, appreciate the clarity and you sharing your schema. It look like you could either save the new record beforehand OR just annotate the ID field with @primaryKey, which will allow you to manage it yourself.
You could then import in the uuid library and set the id field manually. The uuid library comes with DataStore, so it doesn't need to be installed separately (as seen in the code example below). This way you can accomplish your intended use case with a single save.
import { v4 } from 'uuid';
const id = v4();
const key = 'recordings/' + id + '.' + fileType;
const recording = new Recording({
name: title,
description: desc,
date: new Date().toISOString(),
fileName: file.name,
fileKey: key,
speakerCount: speakerCount,
languageCode: languageCode,
});
await DataStore.save(recording);
Let me know if this helps!
Thank you, I think that I will continue to use the method mentioned with saving it. Although I still think it's weird that it just fails with a warning when trying to copy it.
An update to this issue: I've found that saving the object both before and after updating the fileKey, actually doesn't save it on the second save. It doesn't display any errors and doesn't indicate anything is wrong. But when I look in Amplify Studio at the saved recording, it misses the fileKey value.
I would appreciate it if this was looked at as a bug and not just a General question, I would personally call this, and the original way I did it, unexpected behaviour. Thank you in advance.
Agree with @JoakimMellonn , I spent an entire afternoon debugging this. In summary, the issue is "For model instance that is init locally without saving into DataStore, if run copyOf method on it, the updated instance can't be saved into DataStore anymore." @cwomack Please check whether it's a bug or limitation, for bug please fix it and for limitation please mention it in the document, thanks!
Share my case, my model name is "DailyPlan"
type DailyPlan @model @auth(rules: [{allow: owner}]) {
id: ID!
date: AWSDate!
plan: String!
}
const currentDailyPlan = new DailyPlan({ date: "2023-10-23", plan: "test" });
const updatedDailyPlan = DailyPlan.copyOf(currentDailyPlan, updated => { updated.plan = "test 2"; });
await DataStore.save(currentDailyPlan) will success.
await DataStore.save(updatedDailyPlan) will fail with warning message. Variable 'input' has coerced Null value for NonNull type 'AWSDate!'"
I think this pattern (creating model first use in UI, updated its content from user input, then save) is quite common for frontend development. Without supporting this pattern, developer needs to define their own model first then transform back into amplify generated models, this causes duplicate model for same data.
@cwomack would you have updates?
Updated my comments, tended we should fix this limitation.
@JoakimMellonn, after reviewing this further... I believe the issue here is that we don't support mutating a record before it's saved. You need to do a save then an update.
Is there any chance we can get steps for the reproduction of where you say "saving the object both before and after updating the fileKey, actually doesn't save it on the second save"? We'd like to see how you're doing that second save (or both saves ideally) so we can reproduce. Do you see both network requests for the 1st and 2nd save and do they look as expected? Curious to know if the version number is incrementing between the 2 requests.
@sjdeak, similar to the above comment... I think you're currently doing this:
- new TODO (without save)
- make several copy-of's
- save
But I believe you'd need to change your flow to be the following:
- new TODO (with save)
- make several copy-of's
- save
Can you try that change and see if that resolves the issue for you? Is there anything we're missing for your use case that doesn't make the initial save an acceptable solution?
@JoakimMellonn, after reviewing this further... I believe the issue here is that we don't support mutating a record before it's saved. You need to do a save then an update.
This is okay, but I would then request that it gets supported in the future, I personally think that it would be an intuitive thing to support, maybe that's just my opinion.
Is there any chance we can get steps for the reproduction of where you say "saving the object both before and after updating the fileKey, actually doesn't save it on the second save"? We'd like to see how you're doing that second save (or both saves ideally) so we can reproduce. Do you see both network requests for the 1st and 2nd save and do they look as expected? Curious to know if the version number is incrementing between the 2 requests.
Of course! Here is the way I'm doing it now, which actually works. But only because i query the recording before updating it and then update the queried recording, instead of the one I just saved.
const recording = new Recording({
name: title,
description: desc,
date: new Date().toISOString(),
fileName: file.name,
fileKey: '',
speakerCount: speakerCount,
languageCode: languageCode
});
try {
await DataStore.save(recording);
} catch (err) {
console.error(`Error while saving recording first time: ${err}`);
}
...
try {
const key = 'recordings/' + recording.id + '.' + fileType;
const uploadedRecording = (await DataStore.query(Recording, (r) => r.id.eq(recording.id)))[0]; // without getting the recording to then copy it, it doesn't work.
const updatedRecording = Recording.copyOf(uploadedRecording, copy => {
copy.fileKey = key
});
...
Hi @JoakimMellonn 👋 just wanted to follow up and see where we're at.
It sounds like you are able to successfully create and update records but I'd like to confirm that you are unblocked after the guidance provided.
If so, I can convert this issue to a feature request for the ability to mutate a record before it's been saved to the local store.
Hi @JoakimMellonn 👋 just wanted to follow up and see where we're at.
It sounds like you are able to successfully create and update records but I'd like to confirm that you are unblocked after the guidance provided.
If so, I can convert this issue to a feature request for the ability to mutate a record before it's been saved to the local store.
Hi sorry for the late reply. I can confirm that I'm currently unblocked by this, and thank you.