@@ -16,8 +16,9 @@ public struct AppSyncModelMetadata: Codable {
1616} 
1717
1818/// Metadata that contains partial information of a model
19- public  struct  AppSyncPartialModelMetadata :  Codable  { 
20-     let  identifier :  String 
19+ // TODO this should expand to more than just the identifier for composite keys.
20+ public  struct  AppSyncModelIdentifierMetadata :  Codable  { 
21+     let  identifiers :  [ String :  String ] 
2122    let  apiName :  String ? 
2223} 
2324
@@ -28,7 +29,7 @@ public struct AppSyncModelMetadataUtils {
2829    // It needs to have the Model type from `__typename` so it can populate it as "associatedField"
2930    //
3031    // This check is currently broken for CPK use cases since the identifier may not be named `id` anymore
31-     // and also  can be a composite key made up of multiple fields.
32+     // and can be a composite key made up of multiple fields.
3233    static  func  shouldAddMetadata( toModel graphQLData:  JSONValue )  ->  Bool  { 
3334        guard  case let  . object( modelJSON)  =  graphQLData, 
3435              case let  . string( modelName)  =  modelJSON [ " __typename " ] , 
@@ -81,17 +82,29 @@ public struct AppSyncModelMetadataUtils {
8182        encoder. dateEncodingStrategy =  ModelDateFormatting . encodingStrategy
8283        let  decoder  =  JSONDecoder ( ) 
8384        decoder. dateDecodingStrategy =  ModelDateFormatting . decodingStrategy
84-         // Iterate over the associations of the model and for each association, store its association data when 
85-         // the object at  the association is empty. For example, if  the modelType is a Post and has a field that is an 
86-         // array association like Comment, store the post's identifier and  the ModelField name of the parent, ie .
87-         // "post" in  the comments object as metadata .
85+         
86+         // Iterate over  the associations of  the model and for each association, either create the identifier metadata 
87+         // for lazy loading belongs-to or create  the model association metadata for lazy loading has-many .
88+         // The metadata gets decoded to  the LazyModel and List implementations respectively .
8889        for  modelField  in  modelSchema. fields. values { 
8990
91+             // Handle Belongs-to associations. For the current `modelField` that is a belongs-to association,
92+             // retrieve the data and attempt to decode to the association's modelType. If it can be decoded,
93+             // this means it is eager loaded and does not need to be lazy loaded. If it cannot, extract the
94+             // identifiers out of the data in the AppSyncModelIdentifierMetadata and store that in place for
95+             // the LazyModel to decode from.
9096            if  !modelField. isArray && modelField. hasAssociation, 
9197               let  nestedModelJSON =  modelJSON [ modelField. name] , 
92-                let  partialModelMetadata =  isPartialModel ( nestedModelJSON,  apiName:  apiName)  { 
93-             
94-                 if  let  serializedMetadata =  try ? . encode ( partialModelMetadata) , 
98+                case . object( let  modelObject)  =  nestedModelJSON, 
99+                let  associatedModelName =  modelField. associatedModelName, 
100+                let  associatedModelType =  ModelRegistry . modelType ( from:  associatedModelName) , 
101+                let  serializedModelObject =  try ? . encode ( modelObject) , 
102+                !( ( try ? . decode ( associatedModelType. self,  from:  serializedModelObject) )  !=  nil ) , 
103+                let  modelIdentifierMetadata =  containsOnlyIdentifiers ( associatedModelType, 
104+                                                                      modelObject:  modelObject, 
105+                                                                      apiName:  apiName)  { 
106+                 
107+                 if  let  serializedMetadata =  try ? . encode ( modelIdentifierMetadata) , 
95108                   let  metadataJSON =  try ? . decode ( JSONValue . self,  from:  serializedMetadata)  { 
96109                    modelJSON. updateValue ( metadataJSON,  forKey:  modelField. name) 
97110                }  else  { 
@@ -101,6 +114,11 @@ public struct AppSyncModelMetadataUtils {
101114                } 
102115            } 
103116
117+             // Handle Has-many. Store the association data (parent's identifier and field name) only when the model
118+             // at the association is empty. If it's not empty, that means the has-many has been eager loaded.
119+             // For example, when traversing the Post's fields and encounters the has-many association Comment, store
120+             // the association metadata containing the post's identifier at comment, to be decoded to the List
121+             // This allows the list to perform lazy loading of the Comments with a filter on the post's identifier.
104122            if  modelField. isArray && modelField. hasAssociation, 
105123               let  associatedField =  modelField. associatedField, 
106124               modelJSON [ modelField. name]  ==  nil  { 
@@ -121,24 +139,25 @@ public struct AppSyncModelMetadataUtils {
121139        return  JSONValue . object ( modelJSON) 
122140    } 
123141
124-     // A partial model is when only the values of the identifier of the model exists, and nothing else.
125-     // Traverse of the primary keys of the model, and check if there's exactly those values exists.
126-     // This means that the model are missing required/optional fields that are not the identifier of the model.
127-     // TODO: This code needs to account for CPK.
128-     static  func  isPartialModel( _ modelJSON:  JSONValue ,  apiName:  String ? )  ->  AppSyncPartialModelMetadata ? { 
129-         guard  case . object( let  modelObject)  =  modelJSON else  { 
130-             return  nil 
131-         } 
142+     // At this point, we know the model cannot be decoded to the fully model
143+     // so extract the primary key and values out.
144+     static  func  containsOnlyIdentifiers( _ associatedModel:  Model . Type , 
145+                                         modelObject:  [ String :  JSONValue ] , 
146+                                         apiName:  String ? )  ->  AppSyncModelIdentifierMetadata ? { 
147+         let  primarykeys  =  associatedModel. schema. primaryKey
148+         print ( " primaryKeys  \( primarykeys) " ) 
132149
133-         // TODO: This should be based on the number of fields that make up the identifier + __typename
134-         guard  modelObject. count ==  2  else  { 
135-             return  nil 
150+         var  identifiers  =  [ String:  String] ( ) 
151+         for  identifierField  in  primarykeys. fields { 
152+             if  case . string( let  id)  =  modelObject [ identifierField. name]  { 
153+                 print ( " Found key value  \( identifierField. name)  value:  \( id) " ) 
154+                 identifiers. updateValue ( id,  forKey:  identifierField. name) 
155+             } 
136156        } 
137-         
138-         if  case . string( let  id)  =  modelObject [ " id " ]  { 
139-             return  AppSyncPartialModelMetadata ( identifier:  id,  apiName:  apiName) 
157+         if  !identifiers. isEmpty { 
158+             return  AppSyncModelIdentifierMetadata ( identifiers:  identifiers,  apiName:  apiName) 
159+         }  else  { 
160+             return  nil 
140161        } 
141-         
142-         return  nil 
143162    } 
144163} 
0 commit comments