|
| 1 | +// |
| 2 | +// Copyright Amazon.com Inc. or its affiliates. |
| 3 | +// All Rights Reserved. |
| 4 | +// |
| 5 | +// SPDX-License-Identifier: Apache-2.0 |
| 6 | +// |
| 7 | + |
| 8 | +import Amplify |
| 9 | +import Dispatch |
| 10 | +import AWSPluginsCore |
| 11 | + |
| 12 | +extension MutationEvent { |
| 13 | + // Consecutive operations that modify a model results in a sequence of pending mutation events that |
| 14 | + // have the current version of the model. The first mutation event has the correct version of the model, |
| 15 | + // while the subsequent events will have lower versions if the first mutation event is successfully synced |
| 16 | + // to the cloud. By reconciling the pending mutation events after syncing the first mutation event, |
| 17 | + // we attempt to update the pending version to the latest version from the response. |
| 18 | + // The before and after conditions for consecutive update scenarios are as below: |
| 19 | + // - Save, then immediately update |
| 20 | + // Queue Before - [(version: nil, inprocess: true, type: .create), |
| 21 | + // (version: nil, inprocess: false, type: .update)] |
| 22 | + // Response - [version: 1, type: .create] |
| 23 | + // Queue After - [(version: 1, inprocess: false, type: .update)] |
| 24 | + // - Save, then immediately delete |
| 25 | + // Queue Before - [(version: nil, inprocess: true, type: .create), |
| 26 | + // (version: nil, inprocess: false, type: .delete)] |
| 27 | + // Response - [version: 1, type: .create] |
| 28 | + // Queue After - [(version: 1, inprocess: false, type: .delete)] |
| 29 | + // - Save, sync, then immediately update and delete |
| 30 | + // Queue Before (After save, sync) |
| 31 | + // - [(version: 1, inprocess: true, type: .update), (version: 1, inprocess: false, type: .delete)] |
| 32 | + // Response - [version: 2, type: .update] |
| 33 | + // Queue After - [(version: 2, inprocess: false, type: .delete)] |
| 34 | + // |
| 35 | + // For a given model `id`, checks the version of the head of pending mutation event queue |
| 36 | + // against the API response version in `mutationSync` and saves it in the mutation event table if |
| 37 | + // the response version is a newer one |
| 38 | + static func reconcilePendingMutationEventsVersion(sent mutationEvent: MutationEvent, |
| 39 | + received mutationSync: MutationSync<AnyModel>, |
| 40 | + storageAdapter: StorageEngineAdapter, |
| 41 | + completion: @escaping DataStoreCallback<Void>) { |
| 42 | + MutationEvent.pendingMutationEvents( |
| 43 | + forMutationEvent: mutationEvent, |
| 44 | + storageAdapter: storageAdapter |
| 45 | + ) { queryResult in |
| 46 | + switch queryResult { |
| 47 | + case .failure(let dataStoreError): |
| 48 | + completion(.failure(dataStoreError)) |
| 49 | + case .success(let localMutationEvents): |
| 50 | + guard let existingEvent = localMutationEvents.first else { |
| 51 | + completion(.success(())) |
| 52 | + return |
| 53 | + } |
| 54 | + |
| 55 | + guard let reconciledEvent = reconcile(pendingMutationEvent: existingEvent, |
| 56 | + with: mutationEvent, |
| 57 | + responseMutationSync: mutationSync) else { |
| 58 | + completion(.success(())) |
| 59 | + return |
| 60 | + } |
| 61 | + |
| 62 | + storageAdapter.save(reconciledEvent, condition: nil, eagerLoad: true) { result in |
| 63 | + switch result { |
| 64 | + case .failure(let dataStoreError): |
| 65 | + completion(.failure(dataStoreError)) |
| 66 | + case .success: |
| 67 | + completion(.success(())) |
| 68 | + } |
| 69 | + } |
| 70 | + } |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + static func reconcile(pendingMutationEvent: MutationEvent, |
| 75 | + with requestMutationEvent: MutationEvent, |
| 76 | + responseMutationSync: MutationSync<AnyModel>) -> MutationEvent? { |
| 77 | + // return if version of the pending mutation event is not nil and |
| 78 | + // is >= version contained in the response |
| 79 | + if pendingMutationEvent.version != nil && |
| 80 | + pendingMutationEvent.version! >= responseMutationSync.syncMetadata.version { |
| 81 | + return nil |
| 82 | + } |
| 83 | + |
| 84 | + do { |
| 85 | + let responseModel = responseMutationSync.model.instance |
| 86 | + let requestModel = try requestMutationEvent.decodeModel() |
| 87 | + |
| 88 | + // check if the data sent in the request is the same as the response |
| 89 | + // if it is, update the pending mutation event version to the response version |
| 90 | + guard let modelSchema = ModelRegistry.modelSchema(from: requestMutationEvent.modelName), |
| 91 | + modelSchema.compare(responseModel, requestModel) else { |
| 92 | + return nil |
| 93 | + } |
| 94 | + |
| 95 | + var pendingMutationEvent = pendingMutationEvent |
| 96 | + pendingMutationEvent.version = responseMutationSync.syncMetadata.version |
| 97 | + return pendingMutationEvent |
| 98 | + } catch { |
| 99 | + Amplify.log.verbose("Error decoding models: \(error)") |
| 100 | + return nil |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | +} |
0 commit comments