'Amplify ID for a @belongTo (to one) relationship not in the generated Typescript model

I have an error message from VSCode when using the "foreign key" property for storing an object. I've implemented the exact example from their documentation: https://docs.amplify.aws/lib/datastore/relational/q/platform/js/#updated-schema

It's 2 entities: Post and Comment with a bidirectional relationship:

  • Post ---@hasMany---> Comment
  • Post <--@belongsTo-- Comment

Here is the code copy/pasted from the docs into schema.graphql:

input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!

enum PostStatus {
  ACTIVE
  INACTIVE
}

type Post @model {
  id: ID!
  title: String!
  rating: Int!
  status: PostStatus!
  # New field with @hasMany
  comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"])
}

# New model
type Comment @model {
  id: ID!
  postID: ID! @index(name: "byPost", sortKeyFields: ["content"])
  post: Post! @belongsTo(fields: ["postID"])
  content: String!
}

My .ts file with the content of the function copy/pasted from their documentation:

import { DataStore } from "aws-amplify"
import { Post, Comment, PostStatus } from "./models/index"

async function createPost() {
    const post = await DataStore.save(
        new Post({
          title: "My Post with comments",
          rating: 10,
          status: PostStatus.ACTIVE
        })
      );
  
    await DataStore.save(
        new Comment({
            content: "Loving Amplify DataStore!",
            postID: post.id  // <<<<<<<<<<<< Compile error
        })
    );
}

On the postID proprety, VSCode complains with the followind error.

Argument of type '{ content: string; postID: string; }' is not assignable to parameter of type 'ModelInit<Comment, CommentMetaData>'.
  Object literal may only specify known properties, but 'postID' does not exist in type 'ModelInit<Comment, CommentMetaData>'. Did you mean to write 'post'?ts(2345)

Indeed, Comment.postID is not defined in the src/models/index.d.ts file generated by Amplify codegen model. Comment.post exists, but not Comment.postID.

export declare class Post {
  readonly id: string;
  readonly title: string;
  readonly rating: number;
  readonly status: PostStatus | keyof typeof PostStatus;
  readonly comments?: (Comment | null)[];
  readonly createdAt?: string;
  readonly updatedAt?: string;
  constructor(init: ModelInit<Post, PostMetaData>);
  static copyOf(source: Post, mutator: (draft: MutableModel<Post, PostMetaData>) => MutableModel<Post, PostMetaData> | void): Post;
}

export declare class Comment {
  readonly id: string;
  readonly post: Post;
  readonly content: string;
  readonly createdAt?: string;
  readonly updatedAt?: string;
  constructor(init: ModelInit<Comment, CommentMetaData>);
  static copyOf(source: Comment, mutator: (draft: MutableModel<Comment, CommentMetaData>) => MutableModel<Comment, CommentMetaData> | void): Comment;
}

VSCode does not complain anymore if I rename my file using the DataStore as a .js instead of a .ts.

If add the Comment.postId attribute manually in the generated index.d.ts

  export declare class Comment {
    readonly id: string;
    readonly post: Post;
    readonly postID: string;

then the error disappears but another one comes:

Argument of type '{ content: string; postID: string; }' is not assignable to parameter of type 'ModelInit<Comment, CommentMetaData>'.
  Property 'post' is missing in type '{ content: string; postID: string; }' but required in type 'ModelInit<Comment, CommentMetaData>'.ts(2345)

Indeed: Comment.post has not been assigned (as if I'm supposed to query for the Post in order to assign it to Comment.post instead of just assigning Comment.postId with the id of the comment, which would not be nice).

I'm new to Typescript and Amplify. Any idea what I am doing wrong ? Or is it a documentation mistake ? Or a bug in codgen ?

Thank you.



Solution 1:[1]

I agree that the silence on this is frustrating.

The 2 workarounds that I have used are:

  1. create the model with the @belongsTo(fields:["fieldName"]), push it to the backend, then remove the "fields" decoration and run amplify codegen models. Rinse and repeat when you update the model.

  2. Omit the "fields" decoration and create a custom resolver to connect the data. Once you update your model and run 'amplify codegen models', the api\app\buld\resolvers folder with the the templates for all of the reolvers in use. Copy the *.req and *.res files, for the fields you are looking to map, into the 'api\appname\resolvers folder and update the query to return your lookup data.

Make sure the when you use getitem when looking up an ID primary key field you return the results. When you are using query, make sure to return the items[0] record since the results are a list.

custom resolver request filename "TableName.fieldname.req.vtl"

{ "operation" : "Query", "index":"indexName", "query" : { ## Provide a query expression. ** "expression": "hashKey = :id", "expressionValues" : { ":id" : $util.dynamodb.toDynamoDBJson($ctx.source.value) } } }

custom resolver results filename "TableName.fieldname.res.vtl"

#if( $ctx.error ) $util.error($ctx.error.message, $ctx.error.type) #else #if( !$ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) $util.toJson($ctx.result.items[0]) #else #if( $ctx.result.items.isEmpty() && $ctx.result.scannedCount == 1 ) $util.unauthorized() #end $util.toJson(null) #end #end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1