Skip to content

add DataStore configuration API #369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions Amplify.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
2129BE4423948951006363A1 /* MutationSyncMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE4323948951006363A1 /* MutationSyncMetadata.swift */; };
2129BE48239489AC006363A1 /* MutationSyncMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE47239489AC006363A1 /* MutationSyncMetadataTests.swift */; };
2129BE4F23949F1B006363A1 /* MutationSyncMetadata+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE4B23948C54006363A1 /* MutationSyncMetadata+Schema.swift */; };
2129BE512395A66F006363A1 /* AmplifyModelRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE502395A66F006363A1 /* AmplifyModelRegistration.swift */; };
2129BE512395A66F006363A1 /* ModelSchemaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE502395A66F006363A1 /* ModelSchemaProvider.swift */; };
2129BE552395CAEF006363A1 /* PaginatedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE542395CAEF006363A1 /* PaginatedList.swift */; };
2129BE562395CAF9006363A1 /* PaginatedListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2129BE522395CA05006363A1 /* PaginatedListTests.swift */; };
212CE6FC23E9E523007D8E71 /* SelectionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE6FB23E9E523007D8E71 /* SelectionSet.swift */; };
Expand Down Expand Up @@ -211,6 +211,7 @@
B98E9D142372236300934B51 /* QueryPredicate+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98E9D0C2372236200934B51 /* QueryPredicate+Equatable.swift */; };
B996FC4423FF2FA8006D0F68 /* Encodable+AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B996FC4323FF2FA8006D0F68 /* Encodable+AnyEncodable.swift */; };
B996FC4D24059918006D0F68 /* Model+Enum.swift in Sources */ = {isa = PBXBuildFile; fileRef = B996FC4C24059918006D0F68 /* Model+Enum.swift */; };
B9A329CC243559BF00C5B80C /* TimeInterval+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A329CB243559BF00C5B80C /* TimeInterval+Helper.swift */; };
B9B64A9F23FCBF7E00730B68 /* ModelValueConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B64A9E23FCBF7E00730B68 /* ModelValueConverter.swift */; };
B9DCA263240F217C00075E22 /* AnyEncodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9DCA262240F217C00075E22 /* AnyEncodableTests.swift */; };
B9FAA10B23878122009414B4 /* ModelField+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FAA10A23878122009414B4 /* ModelField+Association.swift */; };
Expand Down Expand Up @@ -559,7 +560,7 @@
2129BE4323948951006363A1 /* MutationSyncMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutationSyncMetadata.swift; sourceTree = "<group>"; };
2129BE47239489AC006363A1 /* MutationSyncMetadataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutationSyncMetadataTests.swift; sourceTree = "<group>"; };
2129BE4B23948C54006363A1 /* MutationSyncMetadata+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MutationSyncMetadata+Schema.swift"; sourceTree = "<group>"; };
2129BE502395A66F006363A1 /* AmplifyModelRegistration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmplifyModelRegistration.swift; sourceTree = "<group>"; };
2129BE502395A66F006363A1 /* ModelSchemaProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelSchemaProvider.swift; sourceTree = "<group>"; };
2129BE522395CA05006363A1 /* PaginatedListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedListTests.swift; sourceTree = "<group>"; };
2129BE542395CAEF006363A1 /* PaginatedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedList.swift; sourceTree = "<group>"; };
212CE6FB23E9E523007D8E71 /* SelectionSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionSet.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -846,6 +847,7 @@
B99209E22411E49D00F80010 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
B996FC4323FF2FA8006D0F68 /* Encodable+AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encodable+AnyEncodable.swift"; sourceTree = "<group>"; };
B996FC4C24059918006D0F68 /* Model+Enum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Model+Enum.swift"; sourceTree = "<group>"; };
B9A329CB243559BF00C5B80C /* TimeInterval+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Helper.swift"; sourceTree = "<group>"; };
B9B64A9E23FCBF7E00730B68 /* ModelValueConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelValueConverter.swift; sourceTree = "<group>"; };
B9DCA262240F217C00075E22 /* AnyEncodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodableTests.swift; sourceTree = "<group>"; };
B9FAA10A23878122009414B4 /* ModelField+Association.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelField+Association.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1740,7 +1742,6 @@
B92E03A62367CE7A006CEB8D /* Model */ = {
isa = PBXGroup;
children = (
2129BE502395A66F006363A1 /* AmplifyModelRegistration.swift */,
B92E03A72367CE7A006CEB8D /* Model.swift */,
B9FAA17F238FBB5D009414B4 /* Model+Array.swift */,
B92E03AC2367CE7A006CEB8D /* Model+Codable.swift */,
Expand All @@ -1750,6 +1751,7 @@
FA8EE77C238627350097E4F1 /* Model+Subscript.swift */,
B92E03A92367CE7A006CEB8D /* ModelRegistry.swift */,
FA5D4CF2238AFD7B00D2F54A /* ModelRegistry+Syncable.swift */,
2129BE502395A66F006363A1 /* ModelSchemaProvider.swift */,
FA8EE780238628490097E4F1 /* Persistable.swift */,
FA8EE775238626C70097E4F1 /* AnyModel */,
B9FAA1232388BE2B009414B4 /* Collection */,
Expand Down Expand Up @@ -2090,6 +2092,7 @@
FAE4145E23999BC900CE94C2 /* Result+Void.swift */,
FA56F72422B14B6A0039754A /* Resumable.swift */,
B9FAA174238EFC59009414B4 /* String+Extensions.swift */,
B9A329CB243559BF00C5B80C /* TimeInterval+Helper.swift */,
219A88EC23F3309800BBC5F2 /* Tree.swift */,
);
path = Support;
Expand Down Expand Up @@ -3655,7 +3658,7 @@
FAF1B88623392F96007F1435 /* SerialDispatcher.swift in Sources */,
FAAFAF2D23904ADF002CF932 /* AtomicValue+Numeric.swift in Sources */,
FA09B94B2322CBEB000E064D /* LoggingCategoryConfiguration.swift in Sources */,
2129BE512395A66F006363A1 /* AmplifyModelRegistration.swift in Sources */,
2129BE512395A66F006363A1 /* ModelSchemaProvider.swift in Sources */,
210922572359693900CEC295 /* BasicAnalyticsEvent.swift in Sources */,
FAC2353F227A055200424678 /* PluginError.swift in Sources */,
FA76A2D32342B47100B91ADB /* HubPayloadEventName.swift in Sources */,
Expand Down Expand Up @@ -3777,6 +3780,7 @@
FAC2351C227A053D00424678 /* APICategoryPlugin.swift in Sources */,
FA176ED52385012000C5C5F9 /* DataStoreCategory+HubPayloadEventName.swift in Sources */,
210DBC162332B3CB009B9E51 /* StorageDownloadDataOperation.swift in Sources */,
B9A329CC243559BF00C5B80C /* TimeInterval+Helper.swift in Sources */,
950A26DC23D15D7E00D92B19 /* PredictionsTextToSpeechOperation.swift in Sources */,
FAC235A3227A5ED000424678 /* HubChannel.swift in Sources */,
FAA2E8CE23A02A8100E420EA /* PredictionsCategory+Resettable.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

/// Protocol that defines a contract between the consumer and the DataStore plugin.
/// All models have to be registered and have an associated `version`.
public protocol AmplifyModelRegistration {
public protocol ModelSchemaProvider {

/// Function called during plugin initialization. Implementations must
/// register all the available models here.
Expand Down
28 changes: 28 additions & 0 deletions Amplify/Core/Support/TimeInterval+Helper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation

extension TimeInterval {

public static func seconds(_ value: Double) -> TimeInterval {
return value
}

public static func minutes(_ value: Double) -> TimeInterval {
return value * 60
}

public static func hours(_ value: Double) -> TimeInterval {
return value * 60 * 60
}

public static func days(_ value: Double) -> TimeInterval {
return value * 60 * 60 * 24
}

}
4 changes: 2 additions & 2 deletions AmplifyPlugins/API/AWSAPICategoryPlugin/AWSAPIPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ final public class AWSAPIPlugin: NSObject, APICategoryPlugin {
}
}

public init(modelRegistration: AmplifyModelRegistration? = nil,
public init(schema: ModelSchemaProvider? = nil,
sessionFactory: URLSessionBehaviorFactory? = nil) {

self.mapper = OperationTaskMapper()
self.queue = OperationQueue()
modelRegistration?.registerModels(registry: ModelRegistry.self)
schema?.registerModels(registry: ModelRegistry.self)

super.init()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
/// The Publisher that sends mutation events to subscribers
var dataStorePublisher: DataStoreSubscribeBehavior?

let modelRegistration: AmplifyModelRegistration
/// The schema that holds a reference to its version and all registered models
let schema: ModelSchemaProvider

/// The DataStore configuration
let configuration: DataStoreConfiguration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On line72, in the function configure(using configuration: Any), we pass in the amplify configuration (I think), so we might want to rename this to something like dataStoreConfiguration


/// The local storage provider. Resolved during configuration phase
var storageEngine: StorageEngineBehavior!
Expand All @@ -38,8 +42,9 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
}

/// No-argument init that uses defaults for all providers
public init(modelRegistration: AmplifyModelRegistration) {
self.modelRegistration = modelRegistration
public init(schema: ModelSchemaProvider, configuration: DataStoreConfiguration = .default) {
self.schema = schema
self.configuration = configuration
self.isSyncEnabled = false
if #available(iOS 13.0, *) {
self.dataStorePublisher = DataStorePublisher()
Expand All @@ -49,10 +54,12 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
}

/// Internal initializer for testing
init(modelRegistration: AmplifyModelRegistration,
init(schema: ModelSchemaProvider,
configuration: DataStoreConfiguration = .default,
storageEngine: StorageEngineBehavior,
dataStorePublisher: DataStoreSubscribeBehavior) {
self.modelRegistration = modelRegistration
self.schema = schema
self.configuration = configuration
self.isSyncEnabled = false
self.storageEngine = storageEngine
self.dataStorePublisher = dataStorePublisher
Expand All @@ -62,7 +69,7 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
/// `AmplifyModelRegistration.registerModels`, so we can inspect those models to derive isSyncEnabled, and pass
/// them to `StorageEngine.setUp(models:)`
public func configure(using configuration: Any) throws {
modelRegistration.registerModels(registry: ModelRegistry.self)
schema.registerModels(registry: ModelRegistry.self)
resolveSyncEnabled()
try resolveStorageEngine()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Amplify
import Foundation

/// Error Handler function typealias
public typealias DataStoreErrorHandler = (AmplifyError) -> Void

/// Holds a reference to both the local `Model` and the remote one during a conflict
/// resolution. Implementations of the `DataStoreConflictHandler` use this to decide
/// what the outcome of a conflict should be.
public struct DataStoreConclictData {
public let local: Model
public let remote: Model
}

/// Conflict Handler function typealias. The function is used during a conflict that
/// could not be resolved and requires a decision from the consumer.
public typealias DataStoreConflictHandler = (DataStoreConclictData, DataStoreConflictHandlerResolver) -> Void

/// Callback for the `DataStoreConflictHandler`.
public typealias DataStoreConflictHandlerResolver = (DataStoreConflictHandlerResult) -> Void

/// The conflict resolution result enum.
public enum DataStoreConflictHandlerResult {

/// Discard the local changes in favor of the remote ones.
case discard
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be very explicit: If this discards local changes in favor of remote ones, does that mean updating the system, at the time of the conflict resolution handler, with the state of the remote item? If so then let's document that. Better yet, maybe more appropriate names would be: applyRemote, retryLocal, and retry(Model)?


/// Keep the local changes (semantic shortcut to `retry(local)`).
case keep

/// Return a new `Model` instance that should used instead of the local and remote changes.
case retry(Model)
}

/// The `DataStore` plugin configuration object.
public struct DataStoreConfiguration {

/// A callback function called on unhandled errors
public let errorHandler: DataStoreErrorHandler

/// A callback called when a conflict could not be resolved by the service
public let conflictHandler: DataStoreConflictHandler

/// How often the sync engine will run (in seconds)
public let syncInterval: TimeInterval

/// The number of records to sync per execution
public let syncMaxRecords: UInt

/// The page size of each sync execution
public let syncPageSize: UInt

init(errorHandler: @escaping DataStoreErrorHandler,
conflictHandler: @escaping DataStoreConflictHandler,
syncInterval: TimeInterval,
syncMaxRecords: UInt,
syncPageSize: UInt) {
self.errorHandler = errorHandler
self.conflictHandler = conflictHandler
self.syncInterval = syncInterval
self.syncMaxRecords = syncMaxRecords
self.syncPageSize = syncPageSize
}

}

extension DataStoreConfiguration {

public static let defaultSyncInterval: TimeInterval = .hours(24)
public static let defaultSyncMaxRecords: UInt = 10_000
public static let defaultSyncPageSize: UInt = 1_000

/// Creates a custom configuration. The only required property is `conflictHandler`.
///
/// - Parameters:
/// - errorHandler: a callback function called on unhandled errors
/// - conflictHandler: a callback called when a conflict could not be resolved by the service
/// - syncInterval: how often the sync engine will run (in seconds)
/// - syncMaxRecords: the number of records to sync per execution
/// - syncPageSize: the page size of each sync execution
/// - Returns: an instance of `DataStoreConfiguration` with the passed parameters.
public static func custom(
errorHandler: @escaping DataStoreErrorHandler = { error in
Amplify.Logging.error(error: error)
},
conflictHandler: @escaping DataStoreConflictHandler = { _, resolve in
resolve(.discard)
},
syncInterval: TimeInterval = DataStoreConfiguration.defaultSyncInterval,
syncMaxRecords: UInt = DataStoreConfiguration.defaultSyncMaxRecords,
syncPageSize: UInt = DataStoreConfiguration.defaultSyncPageSize
) -> DataStoreConfiguration {
return DataStoreConfiguration(errorHandler: errorHandler,
conflictHandler: conflictHandler,
syncInterval: syncInterval,
syncMaxRecords: syncMaxRecords,
syncPageSize: syncPageSize)
}

/// The default configuration.
public static var `default`: DataStoreConfiguration {
.custom()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ class SyncEngineIntegrationTestBase: XCTestCase {
Amplify.Logging.logLevel = .verbose

do {
try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: TestModelRegistration()))
try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: TestModelRegistration()))
try Amplify.add(plugin: AWSAPIPlugin())
try Amplify.add(plugin: AWSDataStorePlugin(schema: TestSchemaProvider()))
} catch {
XCTFail(String(describing: error))
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ extension APICategoryDependencyTests {
syncEngine: syncEngine)

let dataStorePublisher = DataStorePublisher()
let dataStorePlugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(),
storageEngine: storageEngine,
dataStorePublisher: dataStorePublisher)
let dataStorePlugin = AWSDataStorePlugin(schema: TestSchemaProvider(),
storageEngine: storageEngine,
dataStorePublisher: dataStorePublisher)
try Amplify.add(plugin: dataStorePlugin)

let dataStoreConfig = DataStoreCategoryConfiguration(plugins: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class InitialSyncOrchestratorTests: XCTestCase {
override class func setUp() {
Amplify.Logging.logLevel = .info
ModelRegistry.reset()
PostCommentModelRegistration().registerModels(registry: ModelRegistry.self)
PostCommentSchemaProvider().registerModels(registry: ModelRegistry.self)
}

/// - Given: An InitialSyncOrchestrator with a model dependency graph
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ class LocalSubscriptionTests: XCTestCase {
}

let dataStorePublisher = DataStorePublisher()
let dataStorePlugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(),
storageEngine: storageEngine,
dataStorePublisher: dataStorePublisher)
let dataStorePlugin = AWSDataStorePlugin(schema: TestSchemaProvider(),
storageEngine: storageEngine,
dataStorePublisher: dataStorePublisher)

let dataStoreConfig = DataStoreCategoryConfiguration(plugins: [
"awsDataStorePlugin": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ class AWSMutationEventIngesterTests: XCTestCase {
syncEngine: syncEngine)

let publisher = DataStorePublisher()
let dataStorePlugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(),
storageEngine: storageEngine,
dataStorePublisher: publisher)
let dataStorePlugin = AWSDataStorePlugin(schema: TestSchemaProvider(),
storageEngine: storageEngine,
dataStorePublisher: publisher)

try Amplify.add(plugin: apiPlugin)
try Amplify.add(plugin: dataStorePlugin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ extension OutgoingMutationQueueMockStateTest {

let storageEngine = MockStorageEngineBehavior()
let dataStorePublisher = DataStorePublisher()
let dataStorePlugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(),
let dataStorePlugin = AWSDataStorePlugin(schema: TestSchemaProvider(),
storageEngine: storageEngine,
dataStorePublisher: dataStorePublisher)
try Amplify.add(plugin: dataStorePlugin)
Expand Down
Loading