Skip to content

Commit e83e047

Browse files
committed
fix(datastore): observe API mutation event decode to model successfully (#2684)
* chore: enable lazy loading integration tests * fix(datastore): observe API mutation event decode to model successfully * update branches for GH workflows * fix typo
1 parent e1680e1 commit e83e047

File tree

40 files changed

+1161
-141
lines changed

40 files changed

+1161
-141
lines changed

.github/workflows/integ_test_datastore_lazy_load.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: DataStore Lazy Load Tests
22
on:
33
workflow_dispatch:
4+
push:
5+
branches: [data-dev-preview]
46

57
permissions:
68
id-token: write
@@ -27,7 +29,7 @@ jobs:
2729
aws_region: ${{ secrets.AWS_REGION }}
2830
aws_s3_bucket: ${{ secrets.AWS_S3_BUCKET_INTEG }}
2931

30-
datastore-integration-v2-test:
32+
datastore-integration-lazy-load-test:
3133
timeout-minutes: 30
3234
needs: prepare-for-test
3335
runs-on: macos-12
@@ -53,5 +55,3 @@ jobs:
5355
with:
5456
project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp
5557
scheme: AWSDataStorePluginLazyLoadTests
56-
57-

Amplify/Categories/DataStore/Model/Internal/ModelListProvider.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public protocol ModelListProvider {
5252
/// Asynchronously retrieve the next page as a new in-memory List object. Returns a failure if there
5353
/// is no next page of results. You can validate whether the list has another page with `hasNextPage()`.
5454
func getNextPage() async throws -> List<Element>
55+
56+
/// Custom encoder
57+
func encode(to encoder: Encoder) throws
5558
}
5659

5760
/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used
@@ -63,14 +66,16 @@ public struct AnyModelListProvider<Element: Model>: ModelListProvider {
6366
private let loadAsync: () async throws -> [Element]
6467
private let hasNextPageClosure: () -> Bool
6568
private let getNextPageAsync: () async throws -> List<Element>
66-
69+
private let encodeClosure: (Encoder) throws -> Void
70+
6771
public init<Provider: ModelListProvider>(
6872
provider: Provider
6973
) where Provider.Element == Self.Element {
7074
self.getStateClosure = provider.getState
7175
self.loadAsync = provider.load
7276
self.hasNextPageClosure = provider.hasNextPage
7377
self.getNextPageAsync = provider.getNextPage
78+
self.encodeClosure = provider.encode
7479
}
7580

7681
public func getState() -> ModelListProviderState<Element> {
@@ -88,6 +93,10 @@ public struct AnyModelListProvider<Element: Model>: ModelListProvider {
8893
public func getNextPage() async throws -> List<Element> {
8994
try await getNextPageAsync()
9095
}
96+
97+
public func encode(to encoder: Encoder) throws {
98+
try encodeClosure(encoder)
99+
}
91100
}
92101

93102
/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used

Amplify/Categories/DataStore/Model/Internal/ModelProvider.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public protocol ModelProvider {
4747
func load() async throws -> Element?
4848

4949
func getState() -> ModelProviderState<Element>
50+
51+
func encode(to encoder: Encoder) throws
5052
}
5153

5254
/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used
@@ -57,18 +59,25 @@ public struct AnyModelProvider<Element: Model>: ModelProvider {
5759

5860
private let loadAsync: () async throws -> Element?
5961
private let getStateClosure: () -> ModelProviderState<Element>
62+
private let encodeClosure: (Encoder) throws -> Void
6063

6164
public init<Provider: ModelProvider>(provider: Provider) where Provider.Element == Self.Element {
6265
self.loadAsync = provider.load
6366
self.getStateClosure = provider.getState
67+
self.encodeClosure = provider.encode
6468
}
69+
6570
public func load() async throws -> Element? {
6671
try await loadAsync()
6772
}
6873

6974
public func getState() -> ModelProviderState<Element> {
7075
getStateClosure()
7176
}
77+
78+
public func encode(to encoder: Encoder) throws {
79+
try encodeClosure(encoder)
80+
}
7281
}
7382

7483
/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used

Amplify/Categories/DataStore/Model/Lazy/ArrayLiteralListProvider.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,8 @@ public struct ArrayLiteralListProvider<Element: Model>: ModelListProvider {
4545
"Don't call this method",
4646
nil)
4747
}
48+
49+
public func encode(to encoder: Encoder) throws {
50+
try elements.encode(to: encoder)
51+
}
4852
}

Amplify/Categories/DataStore/Model/Lazy/DefaultModelProvider.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,14 @@ public struct DefaultModelProvider<Element: Model>: ModelProvider {
3333
public func getState() -> ModelProviderState<Element> {
3434
loadedState
3535
}
36+
37+
public func encode(to encoder: Encoder) throws {
38+
switch loadedState {
39+
case .notLoaded(let identifiers):
40+
var container = encoder.singleValueContainer()
41+
try container.encode(identifiers)
42+
case .loaded(let element):
43+
try element.encode(to: encoder)
44+
}
45+
}
3646
}

Amplify/Categories/DataStore/Model/Lazy/LazyReference.swift

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public class LazyReference<ModelType: Model>: Codable, _LazyReferenceValue {
8686

8787
// MARK: - Codable implementation
8888

89-
/// Decodable implementation is delegated to the underlying `self.reference`.
89+
/// Decodable implementation is delegated to the ModelProviders.
9090
required convenience public init(from decoder: Decoder) throws {
9191
for modelDecoder in ModelProviderRegistry.decoders.get() {
9292
if let modelProvider = modelDecoder.decode(modelType: ModelType.self, decoder: decoder) {
@@ -107,15 +107,9 @@ public class LazyReference<ModelType: Model>: Codable, _LazyReferenceValue {
107107
self.init(identifiers: nil)
108108
}
109109

110-
/// Encodable implementation is delegated to the underlying `self.reference`.
110+
/// Encodable implementation is delegated to the underlying ModelProviders.
111111
public func encode(to encoder: Encoder) throws {
112-
switch loadedState {
113-
case .notLoaded(let identifiers):
114-
var container = encoder.singleValueContainer()
115-
try container.encode(identifiers)
116-
case .loaded(let element):
117-
try element.encode(to: encoder)
118-
}
112+
try modelProvider.encode(to: encoder)
119113
}
120114

121115
// MARK: - APIs

Amplify/Categories/DataStore/Model/Lazy/List+Model.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,6 @@ public class List<ModelType: Model>: Collection, Codable, ExpressibleByArrayLite
158158
}
159159

160160
public func encode(to encoder: Encoder) throws {
161-
switch loadedState {
162-
case .notLoaded:
163-
try [Element]().encode(to: encoder)
164-
case .loaded(let elements):
165-
try elements.encode(to: encoder)
166-
}
161+
try listProvider.encode(to: encoder)
167162
}
168163
}

AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ public class AppSyncListProvider<Element: Model>: ModelListProvider {
6868
apiName: String? = nil,
6969
limit: Int? = nil,
7070
filter: [String: Any]? = nil) {
71-
self.loadedState = .loaded(elements: elements, nextToken: nextToken, filter: filter)
71+
self.loadedState = .loaded(elements: elements,
72+
nextToken: nextToken,
73+
filter: filter)
7274
self.apiName = apiName
7375
self.limit = limit
7476
}
@@ -100,8 +102,6 @@ public class AppSyncListProvider<Element: Model>: ModelListProvider {
100102
}
101103
}
102104

103-
104-
105105
//// Internal `load` to perform the retrieval of the first page and storing it in memory
106106
func load(associatedIdentifiers: [String],
107107
associatedField: String) async throws -> [Element] {
@@ -146,14 +146,14 @@ public class AppSyncListProvider<Element: Model>: ModelListProvider {
146146
filter: filter)
147147
return listResponse.items
148148
case .failure(let graphQLError):
149-
Amplify.API.log.error(error: graphQLError)
149+
log.error(error: graphQLError)
150150
throw CoreError.listOperation(
151151
"The AppSync response returned successfully with GraphQL errors.",
152152
"Check the underlying error for the failed GraphQL response.",
153153
graphQLError)
154154
}
155155
} catch let apiError as APIError {
156-
Amplify.API.log.error(error: apiError)
156+
log.error(error: apiError)
157157
throw CoreError.listOperation("The AppSync request failed",
158158
"See underlying `APIError` for more details.",
159159
apiError)
@@ -203,13 +203,13 @@ public class AppSyncListProvider<Element: Model>: ModelListProvider {
203203
_ = try await nextPageList.fetch()
204204
return nextPageList
205205
case .failure(let graphQLError):
206-
Amplify.API.log.error(error: graphQLError)
206+
log.error(error: graphQLError)
207207
throw CoreError.listOperation("""
208208
The AppSync request was processed by the service, but the response contained GraphQL errors.
209209
""", "Check the underlying error for the failed GraphQL response.", graphQLError)
210210
}
211211
} catch let apiError as APIError {
212-
Amplify.API.log.error(error: apiError)
212+
log.error(error: apiError)
213213
throw CoreError.listOperation("The AppSync request failed",
214214
"See underlying `APIError` for more details.",
215215
apiError)
@@ -218,6 +218,26 @@ public class AppSyncListProvider<Element: Model>: ModelListProvider {
218218
}
219219
}
220220

221+
public func encode(to encoder: Encoder) throws {
222+
switch loadedState {
223+
case .notLoaded(let associatedIdentifiers, let associatedField):
224+
let metadata = AppSyncListDecoder.Metadata.init(
225+
appSyncAssociatedIdentifiers: associatedIdentifiers,
226+
appSyncAssociatedField: associatedField,
227+
apiName: apiName)
228+
var container = encoder.singleValueContainer()
229+
try container.encode(metadata)
230+
case .loaded(let elements, _, _):
231+
// This does not encode the `nextToken` or `filter`, which means the data is essentially dropped.
232+
// If the encoded list is later decoded, the existing `elements` will be available but will be missing
233+
// the metadata to reflect whether there's a next page or not. Encoding the metadata is rather difficult
234+
// with the filter being `Any` type and is not a call pattern that is recommended/supported. The extent
235+
// of this supported flow is to allow encoding and decoding of the `elements`. To get the
236+
// latest data, make the call to your data source directly through **Amplify.API**.
237+
try elements.encode(to: encoder)
238+
}
239+
}
240+
221241
// MARK: - Helpers
222242

223243
/// Retrieve the column names for the specified field `field` for this schema.
@@ -239,3 +259,5 @@ public class AppSyncListProvider<Element: Model>: ModelListProvider {
239259
}
240260

241261
}
262+
263+
extension AppSyncListProvider: DefaultLogger { }

AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncModelProvider.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ public class AppSyncModelProvider<ModelType: Model>: ModelProvider {
5858
public func getState() -> ModelProviderState<ModelType> {
5959
loadedState
6060
}
61+
62+
public func encode(to encoder: Encoder) throws {
63+
switch loadedState {
64+
case .notLoaded(let identifiers):
65+
var container = encoder.singleValueContainer()
66+
try container.encode(identifiers)
67+
case .loaded(let element):
68+
try element.encode(to: encoder)
69+
}
70+
}
6171
}
6272

6373
extension AppSyncModelProvider: DefaultLogger { }

AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginLazyLoadTests/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
1. `amplify init`
1010

11-
These tests were provisioned with V2 Transform:, and updates to `cli.json`
12-
- "respectprimarykeyattributesonconnectionfield": true
13-
- "TODOlazyLoadiOS": true
11+
These tests were provisioned with V2 Transform, CPK enabled, and with the lazy loading feature flag. Review or make updates to cli.json
12+
13+
"transformerversion":2
14+
"respectprimarykeyattributesonconnectionfield": true
15+
"generateModelsForLazyLoadAndCustomSelectionSet": true
1416

1517
2. `amplify add api`
1618

0 commit comments

Comments
 (0)