Why does Amplify Datastore return null in objects having #hasmany relationships? - flutter

I'm new here and I hope to find a solution to this problem. I am new to Flutter and AWS Amplify as well. Here it goes.
I created a very simple schema using Admin UI. The schema has two models, Order and Item. Order contains many Item(s).
I was surprised that call to get OrderItems() returns null. I read a post elsewhere that said #hasmany relationships do not work with with AWS Amplify and a second query was needed to retrieve #hasmany fields.
The CLI generated models seem to support #hasmany relationships.
Here is the simple schema generated by Amplify:
type Items #model #auth(rules: [{allow: public}]) {
id: ID!
productName: String
quantity: String
orderID: ID #index(name: "byOrder")
}
type Order #model #auth(rules: [{allow: public}]) {
id: ID!
Customer: String
OrderItems: [Items] #hasMany(indexName: "byOrder", fields: ["id"])
}
Amplify generated the corresponding models in my project. For brevity, I will include selected parts of the CLI generated code for the Order object. I don't believe the generated models can be modified. Moreover, If the schema is changed, the model dart project files are overwritten.
#override
String getId() {
return id;
}
String? get Customer {
return _Customer;
}
// Returns null
List<Items>? get OrderItems {
return _OrderItems;
}
It appears that ['OrderItems'] should be populate in Order.fromJson.
Order.fromJson(Map<String, dynamic> json)
: id = json['id'],
_Customer = json['Customer'],
_OrderItems = json['OrderItems'] is List
? (json['OrderItems'] as List)
.where((e) => e?['serializedData'] != null)
.map((e) => Items.fromJson(new Map<String, dynamic>.from(e['serializedData'])))
.toList()
: null;
What am I missing? What is the purpose of this code?
The remainder of the CLI generated code seems to support CRUD operations on both Order and Item.
Map<String, dynamic> toJson() => {
'id': id, 'Customer': _Customer, 'OrderItems': _OrderItems?.map((Items? e) => e?.toJson()).toList()
};
static final QueryField ID = QueryField(fieldName: "order.id");
static final QueryField CUSTOMER = QueryField(fieldName: "Customer");
static final QueryField ORDERITEMS = QueryField(
fieldName: "OrderItems",
fieldType: ModelFieldType(ModelFieldTypeEnum.model, ofModelName: (Items).toString()));
static var schema = Model.defineSchema(define: (ModelSchemaDefinition modelSchemaDefinition) {
modelSchemaDefinition.name = "Order";
modelSchemaDefinition.pluralName = "Orders";
modelSchemaDefinition.authRules = [
AuthRule(
authStrategy: AuthStrategy.PUBLIC,
operations: [
ModelOperation.CREATE,
ModelOperation.UPDATE,
ModelOperation.DELETE,
ModelOperation.READ
])
];
modelSchemaDefinition.addField(ModelFieldDefinition.id());
modelSchemaDefinition.addField(ModelFieldDefinition.field(
key: Order.CUSTOMER,
isRequired: false,
ofType: ModelFieldType(ModelFieldTypeEnum.string)
));
modelSchemaDefinition.addField(ModelFieldDefinition.hasMany(
key: Order.ORDERITEMS,
isRequired: false,
ofModelName: (Items).toString(),
associatedKey: Items.ORDERID
));
});
}
class _OrderModelType extends ModelType<Order> {
const _OrderModelType();
#override
Order fromJson(Map<String, dynamic> jsonData) {
return Order.fromJson(jsonData);
}
}
It appears that all the plumbing is in place but the water is turned off. Like I stated, I'm new at this so...
Is this a bug?
One last thing...
ModelProvider.dart throw this compile time error.
Missing concrete implementations of 'getter ModelProviderInterface.customTypeSchemas' and 'setter ModelProviderInterface.customTypeSchemas'.
Try implementing the missing methods, or make the class abstract
Whenever ModelProvider.dart is regenerated I have to add the following line of code.
#override
List<ModelSchema> customTypeSchemas =[];
Thanks in advance

Apparently, #hasOne and #hasMany directives do not support referencing a model which then references the initial model via #hasOne or #hasMany if DataStore is enabled.
Details: Has-One-Relationship

Related

AWS Amplify Flutter one to many relationship - graphql create - not working?

I am new to AWS Amplify and GraphQL API. I created data models using Amplify Studio. Here is the schema.graphql:
type Language #model #auth(rules: [{allow: public}]) {
id: ID!
name: String
fluency: String
rpm: Float
translatorID: ID #index(name: "byTranslator")
}
enum TranslatorTime {
FULLTIME
FREETIME
PARTTIME
}
type Translator #model #auth(rules: [{allow: public}]) {
id: ID!
userId: String
time: TranslatorTime
Languages: [Language] #hasMany(indexName: "byTranslator", fields: ["id"])
}
I am using graphql api on flutter app to create items in translator model, here is the code i tried:
Future<void> addTranslator(Translator translator) async {
try {
final request = ModelMutations.create(translator);
final response = await Amplify.API.mutate(request: request).response;
final createdTranslator = response.data;
if (createdTranslator == null) {
safePrint('errors: ${response.errors}');
return;
}
safePrint('Mutation result: $createdTranslator');
} on ApiException catch (e) {
safePrint('Mutation failed: $e');
}
}
But the problem is when i create translator item, the list of languages is not saved in the database, since the translator has one-to-many relation with language, its not working, in both tables translator and language. I am creating list of language and passing to the language model. The mutation result is:
Mutation result: Translator {id=156a3fd4-ae09-4579-977c-0ca4bb205aca, userId=7d8d034e-d13c-4f53-87c4-4f4bb9adfe03, time=PARTTIME, createdAt=2023-02-06T10:05:52.329000000Z, updatedAt=2023-02-06T10:05:52.329000000Z}
it does not have Languages. Tried with translatorId and without it.

Relation type in Model is not defined

I am new to Prisma.
I am trying to use Prisma with Nextjs.
But I am facing problems with one to many relations.
I have a schema like below.
model Thread {
id Int #id #default(autoincrement()) #unique
title String
kakikoes Kakiko[]
}
model Kakiko {
id Int #id #default(autoincrement()) #unique
thread Thread #relation(fields: [threadId], references: [id])
threadId Int
}
// Kakiko means "Post" in Japanese.
There is "one to many Relation" which towards "kakiko" from Thread.
I can get datas from DB and I can send datas to browser, But My TS compiler says "TS2339: Property 'kakikoes' does not exist on type 'Thread'."
await prisma.thread.findUnique({
where: {
id: threadId
},
include: {
kakikoes: true
}
})
{thread.kakikoes.map(kakiko => <p>{kakiko.body}</p>)}
I checked type definition, and kakikoes not defined.
/**
* Model Thread
*
*/
export type Thread = {
id: number
itaId: number
title: string
}
How can I solve this?
Thank you.
Added (2022/04/03)
I use Intelij Idea Ultimate.
Type is defined like this.
My code is working correctly on My browser And Nextjs dev Tool doesnt say Any Errors nor Any Warnings.
The model is correct. I have tried it and this is the working solution.
schema.prisma file
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Thread {
id Int #id #unique #default(autoincrement())
title String
kakikoes Kakiko[]
}
model Kakiko {
id Int #id #unique #default(autoincrement())
thread Thread #relation(fields: [threadId], references: [id])
threadId Int
}
main.ts file
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient();
async function main() {
// ... you will write your Prisma Client queries here
await prisma.kakiko.create({
data: {
thread: {
create: {
title: 'test',
},
},
},
});
const thread = await prisma.thread.findUnique({
where: {
id: 1,
},
include: {
kakikoes: true,
},
});
console.log('thread', thread);
}
main()
.catch((e) => {
throw e;
})
.finally(async () => {
await prisma.$disconnect();
});
Here is the response
thread { id: 1, title: 'test', kakikoes: [ { id: 1, threadId: 1 } ] }
Attaching the image as well:
I would also suggest restarting your vs code typescript compiler which could cause this issue.

Unknown argument error when creating record

I'm encountering some interesting behaviour when using Prisma ORM. It is related to Prisma's generated types, and I've been skimming the docs trying to find out more, but there doesn't seem to be much info about generated types in there (please correct me if I'm mistaken). Here's the behaviour:
Say I have a model with two 1-1 relations (Profile in the example below):
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int #id #default(autoincrement())
name String
profile Profile?
}
model Profile {
id Int #id #default(autoincrement())
name String
userId Int?
user User? #relation(fields: [userId], references: [id])
photoId Int?
photo Photo? #relation(fields: [photoId], references: [id])
}
model Photo {
id Int #id #default(autoincrement())
url String
profile Profile?
}
The following code works when creating a new profile:
const user = await prisma.user.create({ data: { name: "TestUser" } });
const profile = await prisma.profile.create({
data: {
name: "TestProfile",
user: { connect: { id: user.id } },
photo: { create: { url: "http://example.com/img" } },
},
});
... but this fails with an error:
const user = await prisma.user.create({ data: { name: "TestUser" } });
const profile = await prisma.profile.create({
data: {
name: "TestProfile",
userId: user.id,
photo: { create: { url: "http://example.com/img" } },
},
});
The error is:
Unknown arg userId in data.userId for type ProfileCreateInput. Did you mean user? Available args:
type ProfileCreateInput {
  name: String
  user?: UserCreateNestedOneWithoutProfileInput
  photo?: PhotoCreateNestedOneWithoutProfileInput
}
Why is the second create-profile code invalid?
Prisma essentially generates two type definitions for a create query. This is implemented with a XOR type, which ensures that only one definition out of two is fully specified and passed to the query:
export type ProfileCreateArgs = {
/* ... */
data: XOR<ProfileCreateInput, ProfileUncheckedCreateInput>;
}
The definitions are called checked and unchecked, the former using nested fields and the latter using raw ids:
export type ProfileCreateInput = {
id?: number;
/* ... */
user?: UserCreateNestedOneWithoutProfileInput;
photo?: PhotoCreateNestedOneWithoutProfileInput;
}
export type ProfileUncheckedCreateInput = {
id?: number;
/* ... */
userId?: number;
photoId?: number;
}
Which basically means that you either provide all references as connect, create etc. relations (checked) or as raw ids (unchecked). You cannot mix the styles, this is not supported.

MongoDB and TypeScript: Decouple a domain entity's id type from MongoDB's ObjectID

Inside my MongoDB repositories, entities have an _id: ObjectID type to be handled properly. However, I would like my domain entities to have a simple id: string attribute to avoid any dependencies on any database or framework. The solution I came up with so far looks as follows:
export interface Book {
id: string;
title: string;
}
// A MongodbEntity<Book> would now have an _id instead of its string id
export type MongodbEntity<T extends { id: string; }> = Omit<T, 'id'> & { _id: ObjectID; };
In my repository this would work:
async findOneById(id: string): Promise<Book | null> {
const res = await this.collection.findOneById({_id: new ObjectId(id)});
return res ? toBook(res) : null;
}
function toBook(dbBook: MongodbEntity<Book>): Book {
const {_id, ...rest} = dbBook;
return {...rest, id: _id.toHexString() };
}
What doesn't work is to make this behavior generic. A converter function like this:
function toDomainEntity<T extends {id: string}>(dbEntity: MongoDbEntity<T>): T {
const {_id, ...rest} = dbEntity;
return {...rest, id: _id.toHexString() };
}
leads to an error described here.
What I am looking for is either a working solution for the generic toDomainEntity function or a different (generic) approach that would let me decouple my domain entity types from MongoDB's _id: ObjectID type.

Can I add non-persistent fields to a model?

Is there a recommended practice for adding non-persistent attributes to an Objection model object such that it that won't overwrite predefined attributes?
Objection models have the virtualAttributes field. From the documentation:
The virtual values are not written to database. Only the “external” JSON format will contain them.
It is important to note these are functions, not just model properties.
Example from the docs:
class Person extends Model {
static get virtualAttributes() {
return ['fullName', 'isFemale'];
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
get isFemale() {
return this.gender === 'female';
}
}
const person = Person.fromJson({
firstName: 'Jennifer',
lastName: 'Aniston',
gender: 'female'
});
console.log(person.toJSON());
// --> {"firstName": "Jennifer", "lastName": "Aniston", "isFemale": true, "fullName": "Jennifer Aniston"}