From 82490a590c20b88ef886464b994fc28846c14d03 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 4 Aug 2025 15:32:08 -0700 Subject: [PATCH 1/3] Improve `SyncEngine.init` - Infer default cloud container using SwiftData - Statically require string identifier primary keyed tables --- Examples/CloudKitDemo/CloudKitDemoApp.swift | 7 +- .../CloudKitPlaygroundApp.swift | 7 +- .../xcshareddata/swiftpm/Package.resolved | 29 +++---- Examples/Reminders/RemindersApp.swift | 21 ++--- README.md | 9 +- .../Documentation.docc/Articles/CloudKit.md | 82 +++++++++---------- .../Articles/CloudKitSharing.md | 12 +-- .../Articles/ComparisonWithSwiftData.md | 10 +-- .../Documentation.docc/SharingGRDBCore.md | 9 +- 9 files changed, 67 insertions(+), 119 deletions(-) diff --git a/Examples/CloudKitDemo/CloudKitDemoApp.swift b/Examples/CloudKitDemo/CloudKitDemoApp.swift index fdcf1dc6..e96f652b 100644 --- a/Examples/CloudKitDemo/CloudKitDemoApp.swift +++ b/Examples/CloudKitDemo/CloudKitDemoApp.swift @@ -30,11 +30,8 @@ struct CloudKitDemoApp: App { try! prepareDependencies { $0.defaultDatabase = try appDatabase() $0.defaultSyncEngine = try SyncEngine( - container: CKContainer(identifier: "iCloud.co.pointfree.SQLiteData.demos.CloudKitDemo"), - database: $0.defaultDatabase, - tables: [ - Counter.self - ] + for: $0.defaultDatabase, + tables: Counter.self ) } return true diff --git a/Examples/CloudKitPlayground/CloudKitPlaygroundApp.swift b/Examples/CloudKitPlayground/CloudKitPlaygroundApp.swift index 1f90aeb0..44c9512f 100644 --- a/Examples/CloudKitPlayground/CloudKitPlaygroundApp.swift +++ b/Examples/CloudKitPlayground/CloudKitPlaygroundApp.swift @@ -11,11 +11,8 @@ struct CloudKitPlaygroundApp: App { prepareDependencies { $0.defaultDatabase = try! appDatabase() $0.defaultSyncEngine = try! SyncEngine( - container: CKContainer( - identifier: "iCloud.co.pointfree.SQLiteData.demos.CloudKitPlayground" - ), - database: $0.defaultDatabase, - tables: [ModelA.self, ModelB.self, ModelC.self] + for: $0.defaultDatabase, + tables: ModelA.self, ModelB.self, ModelC.self ) } } diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0acc6d3c..46621b31 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "69853b99c9eb6c69968432d61f94ae83a716bf02937dd3eaea7567cdf3966f5d", + "originHash" : "cfa986227a2051ca83eae9c181301c75e9fcd30d8f199a7ee1c7b269b548e192", "pins" : [ { "identity" : "combine-schedulers", @@ -73,24 +73,6 @@ "version" : "1.9.2" } }, - { - "identity" : "swift-docc-plugin", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-docc-plugin", - "state" : { - "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", - "version" : "1.4.5" - } - }, - { - "identity" : "swift-docc-symbolkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-docc-symbolkit", - "state" : { - "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", - "version" : "1.0.0" - } - }, { "identity" : "swift-identified-collections", "kind" : "remoteSourceControl", @@ -154,6 +136,15 @@ "version" : "601.0.1" } }, + { + "identity" : "swift-tagged", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-tagged", + "state" : { + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", diff --git a/Examples/Reminders/RemindersApp.swift b/Examples/Reminders/RemindersApp.swift index a1c89133..115e51f3 100644 --- a/Examples/Reminders/RemindersApp.swift +++ b/Examples/Reminders/RemindersApp.swift @@ -1,6 +1,7 @@ import CloudKit import SharingGRDB import SwiftUI +import UIKit @main struct RemindersApp: App { @@ -13,17 +14,12 @@ struct RemindersApp: App { try! prepareDependencies { $0.defaultDatabase = try Reminders.appDatabase() $0.defaultSyncEngine = try SyncEngine( - container: CKContainer( - identifier: "iCloud.co.pointfree.SQLiteData.demos.field-timestamps-2.Reminders" - ), - database: $0.defaultDatabase, - tables: [ - RemindersList.self, - RemindersListAsset.self, - Reminder.self, - Tag.self, - ReminderTag.self, - ] + for: $0.defaultDatabase, + tables: RemindersList.self, + RemindersListAsset.self, + Reminder.self, + Tag.self, + ReminderTag.self ) } } @@ -40,9 +36,6 @@ struct RemindersApp: App { } } - -import UIKit - class AppDelegate: UIResponder, UIApplicationDelegate, ObservableObject { func application( _ application: UIApplication, diff --git a/README.md b/README.md index db3d6062..812dfe1d 100644 --- a/README.md +++ b/README.md @@ -260,13 +260,8 @@ struct MyApp: App { prepareDependencies { $0.defaultDatabase = try! appDatabase() $0.defaultSyncEngine = SyncEngine( - container: CKContainer( - identifier: "iCloud.co.mycompany.MyApp" - ), - database: $0.defaultDatabase, - tables: [ - Item.self, - ] + for: $0.defaultDatabase, + tables: Item.self ) } } diff --git a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md index aaeb2f4d..31573edb 100644 --- a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md +++ b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md @@ -11,43 +11,43 @@ to make, and so an abundance of care must be taken to make sure all devices rema and capable of communicating with each other. Please read the documentation closely and thoroughly to make sure you understand how to best prepare your app for cloud synchronization. -- [Setting up your project](#Setting-up-your-project) -- [Setting up a SyncEngine](#Setting-up-a-SyncEngine) -- [Designing your schema with synchronization in mind](#Designing-your-schema-with-synchronization-in-mind) - - [Primary keys](#Primary-keys) - - [Primary keys on every table](#Primary-keys-on-every-table) - - [Foreign key relationships](#Foreign-key-relationships) -- [Record conflicts](#Record-conflicts) -- [Backwards compatible migrations](#Backwards-compatible-migrations) - - [Adding tables](#Adding-tables) - - [Adding columns](#Adding-columns) - - [Disallowed migrations](#Disallowed-migrations) -- [Sharing records with other iCloud users](#Sharing-records-with-other-iCloud-users) -- [Assets](#Assets) -- [Accessing CloudKit metadata](#Accessing-CloudKit-metadata) -- [How SharingGRDB handles distributed schema scenarios](#How-SharingGRDB-handles-distributed-schema-scenarios) -- [Unit testing and Xcode previews](#Unit-testing-and-Xcode-previews) -- [Preparing an existing schema for synchronization](#Preparing-an-existing-schema-for-synchronization) - - [Convert Int primary keys to UUID](#Convert-Int-primary-keys-to-UUID) - - [Add primary key to all tables](#Add-primary-key-to-all-tables) -- [Migrating from Swift Data to SharingGRDB](#Migrating-from-Swift-Data-to-SharingGRDB) -- [Separating schema migrations from data migrations](#Separating-schema-migrations-from-data-migrations) -- [Tips and tricks](#Tips-and-tricks) - - [Updating triggers to be compatible with synchronization](#Updating-triggers-to-be-compatible-with-synchronization) -- [Topics](#Topics) - - [Go deeper](#Go-deeper) + * [Setting up your project](#Setting-up-your-project) + * [Setting up a SyncEngine](#Setting-up-a-SyncEngine) + * [Designing your schema with synchronization in mind](#Designing-your-schema-with-synchronization-in-mind) + * [Primary keys](#Primary-keys) + * [Primary keys on every table](#Primary-keys-on-every-table) + * [Foreign key relationships](#Foreign-key-relationships) + * [Record conflicts](#Record-conflicts) + * [Backwards compatible migrations](#Backwards-compatible-migrations) + * [Adding tables](#Adding-tables) + * [Adding columns](#Adding-columns) + * [Disallowed migrations](#Disallowed-migrations) + * [Sharing records with other iCloud users](#Sharing-records-with-other-iCloud-users) + * [Assets](#Assets) + * [Accessing CloudKit metadata](#Accessing-CloudKit-metadata) + * [How SharingGRDB handles distributed schema scenarios](#How-SharingGRDB-handles-distributed-schema-scenarios) + * [Unit testing and Xcode previews](#Unit-testing-and-Xcode-previews) + * [Preparing an existing schema for synchronization](#Preparing-an-existing-schema-for-synchronization) + * [Convert Int primary keys to UUID](#Convert-Int-primary-keys-to-UUID) + * [Add primary key to all tables](#Add-primary-key-to-all-tables) + * [Migrating from Swift Data to SharingGRDB](#Migrating-from-Swift-Data-to-SharingGRDB) + * [Separating schema migrations from data migrations](#Separating-schema-migrations-from-data-migrations) + * [Tips and tricks](#Tips-and-tricks) + * [Updating triggers to be compatible with synchronization](#Updating-triggers-to-be-compatible-with-synchronization) + * [Topics](#Topics) + * [Go deeper](#Go-deeper) ## Setting up your project The steps to set up your SharingGRDB project for CloudKit synchronization are the [same for setting up][setup-cloudkit-apple] any other kind of project for CloudKit: -* Follow the [Configuring iCloud services] guide for enabling iCloud entitlements in your project. -* Follow the [Configuring background execution modes] guide for adding the Background Modes -capability to your project. -* If you want to enable sharing of records with other iCloud users, be sure to add a -`CKSharingSupported` key to your Info.plist with a value of `true`. This is subtly documented -in [Apple's documentation for sharing]. + * Follow the [Configuring iCloud services] guide for enabling iCloud entitlements in your project. + * Follow the [Configuring background execution modes] guide for adding the Background Modes + capability to your project. + * If you want to enable sharing of records with other iCloud users, be sure to add a + `CKSharingSupported` key to your Info.plist with a value of `true`. This is subtly documented + in [Apple's documentation for sharing]. With those steps completed, you are ready to configure a ``SyncEngine`` that will facilitate synchronizing your database to and from CloudKit. @@ -78,19 +78,13 @@ struct MyApp: App { try! prepareDependencies { $0.defaultDatabase = try appDatabase() $0.defaultSyncEngine = try SyncEngine( - container: CKContainer( - identifier: "iCloud.co.pointfree.sharing-grdb.Reminders" - ), - database: $0.defaultDatabase, - tables: [ - RemindersList.self, - Reminder.self, - ] + for: $0.defaultDatabase, + tables: RemindersList.self, Reminder.self ) } } - … + // ... } ``` @@ -100,10 +94,10 @@ has more options you may be interested in configuring. > Important: A few important things to note about this: > -> * The CloudKit container identifier must be explicitly provided and unfortunately cannot be -> extracted from Entitlements.plist automatically. That privilege is only afforded to SwiftData. -> * You must explicitly provide all tables that you want to synchronize. We do this so that you can -> have the option of having some local tables that are not synchronized to CloudKit. +> * The CloudKit container identifier must be explicitly provided and unfortunately cannot be +> extracted from Entitlements.plist automatically. That privilege is only afforded to SwiftData. +> * You must explicitly provide all tables that you want to synchronize. We do this so that you +> can have the option of having some local tables that are not synchronized to CloudKit. Once this work is done the app should work exactly as it did before, but now any changes made to the database will be synchronized to CloudKit. You will still interact with your local SQLite diff --git a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md index 4072ba3a..56ab876f 100644 --- a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md +++ b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md @@ -391,17 +391,9 @@ struct MyApp: App { try! prepareDependencies { $0.defaultDatabase = try appDatabase() $0.defaultSyncEngine = try SyncEngine( - container: CKContainer( - identifier: "iCloud.co.pointfree.sharing-grdb.Reminders" - ), database: $0.defaultDatabase, - tables: [ - RemindersList.self, - Reminder.self, - ], - privateTables: [ - RemindersListPrivate.self - ] + tables: RemindersList.self, Reminder.self, + privateTables: RemindersListPrivate.self ) } } diff --git a/Sources/SharingGRDBCore/Documentation.docc/Articles/ComparisonWithSwiftData.md b/Sources/SharingGRDBCore/Documentation.docc/Articles/ComparisonWithSwiftData.md index 6934e915..7420f6b9 100644 --- a/Sources/SharingGRDBCore/Documentation.docc/Articles/ComparisonWithSwiftData.md +++ b/Sources/SharingGRDBCore/Documentation.docc/Articles/ComparisonWithSwiftData.md @@ -797,14 +797,8 @@ inspect the Entitlements.plist in order to automatically extract that informatio try! prepareDependencies { $0.defaultDatabase = try appDatabase() $0.defaultSyncEngine = try SyncEngine( - container: CKContainer( - identifier: "iCloud.co.pointfree.sharing-grdb.Reminders" - ), - database: $0.defaultDatabase, - tables: [ - RemindersList.self, - Reminder.self, - ] + for: $0.defaultDatabase, + tables: RemindersList.self, Reminder.self ) } } diff --git a/Sources/SharingGRDBCore/Documentation.docc/SharingGRDBCore.md b/Sources/SharingGRDBCore/Documentation.docc/SharingGRDBCore.md index ef52d9c5..f8f968c5 100644 --- a/Sources/SharingGRDBCore/Documentation.docc/SharingGRDBCore.md +++ b/Sources/SharingGRDBCore/Documentation.docc/SharingGRDBCore.md @@ -184,13 +184,8 @@ struct MyApp: App { prepareDependencies { $0.defaultDatabase = try! appDatabase() $0.defaultSyncEngine = SyncEngine( - container: CKContainer( - identifier: "iCloud.co.mycompany.MyApp" - ), - database: $0.defaultDatabase, - tables: [ - /* ... */ - ] + for: $0.defaultDatabase, + tables: /* ... */ ) } } From 84255acd74f0073ff9252266993880790f36f145 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 4 Aug 2025 15:38:58 -0700 Subject: [PATCH 2/3] wip --- .../CloudKit/DefaultSyncEngine.swift | 28 +++++----- .../SharingGRDBCore/CloudKit/SyncEngine.swift | 51 +++++++++++++++---- .../Documentation.docc/Articles/CloudKit.md | 10 ++-- .../Articles/CloudKitSharing.md | 2 +- 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/Sources/SharingGRDBCore/CloudKit/DefaultSyncEngine.swift b/Sources/SharingGRDBCore/CloudKit/DefaultSyncEngine.swift index f641e3fc..7fe1a580 100644 --- a/Sources/SharingGRDBCore/CloudKit/DefaultSyncEngine.swift +++ b/Sources/SharingGRDBCore/CloudKit/DefaultSyncEngine.swift @@ -1,20 +1,20 @@ #if canImport(CloudKit) -import CloudKit -import Dependencies -import GRDB + import CloudKit + import Dependencies + import GRDB -@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) -extension DependencyValues { - public var defaultSyncEngine: SyncEngine { - get { self[SyncEngine.self] } - set { self[SyncEngine.self] = newValue } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + extension DependencyValues { + public var defaultSyncEngine: SyncEngine { + get { self[SyncEngine.self] } + set { self[SyncEngine.self] = newValue } + } } -} -@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) -extension SyncEngine: TestDependencyKey { - public static var testValue: SyncEngine { - try! SyncEngine(container: .default(), database: DatabaseQueue(), tables: []) + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + extension SyncEngine: TestDependencyKey { + public static var testValue: SyncEngine { + try! SyncEngine(for: DatabaseQueue()) + } } -} #endif diff --git a/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift b/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift index 6dd0951f..6e7e810c 100644 --- a/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift +++ b/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift @@ -4,6 +4,8 @@ import CustomDump import OrderedCollections import OSLog + import StructuredQueriesCore + import SwiftData @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) public final class SyncEngine: Sendable { @@ -24,14 +26,41 @@ let dataManager = Dependency(\.dataManager) - public convenience init( - container: CKContainer, + public convenience init( + for database: any DatabaseWriter, + tables: repeat (each T1).Type, + privateTables: repeat (each T2).Type, + containerIdentifier: String? = nil, defaultZone: CKRecordZone = CKRecordZone(zoneName: "co.pointfree.SQLiteData.defaultZone"), - database: any DatabaseWriter, - logger: Logger = Logger(subsystem: "SQLiteData", category: "CloudKit"), - tables: [any PrimaryKeyedTable.Type], - privateTables: [any PrimaryKeyedTable.Type] = [] - ) throws { + logger: Logger = Logger(subsystem: "SQLiteData", category: "CloudKit") + ) throws + where + repeat (each T1).PrimaryKey.QueryOutput: IdentifierStringConvertible, + repeat (each T2).PrimaryKey.QueryOutput: IdentifierStringConvertible + { + let containerIdentifier = containerIdentifier + ?? ModelConfiguration(groupContainer: .automatic).cloudKitContainerIdentifier + + guard let containerIdentifier else { + throw SchemaError( + reason: .noCloudKitContainer, + debugDescription: """ + No default CloudKit container found. Please add a container identifier to your app's \ + entitlements. + """ + ) + } + + let container = CKContainer(identifier: containerIdentifier) + var allTables: [any PrimaryKeyedTable.Type] = [] + var allPrivateTables: [any PrimaryKeyedTable.Type] = [] + for table in repeat each tables { + allTables.append(table) + } + for privateTable in repeat each privateTables { + allPrivateTables.append(privateTable) + } + let userDatabase = UserDatabase(database: database) try self.init( container: container, @@ -66,8 +95,8 @@ }, userDatabase: userDatabase, logger: logger, - tables: tables, - privateTables: privateTables + tables: allTables, + privateTables: allPrivateTables ) _ = try setUpSyncEngine( userDatabase: userDatabase, @@ -1493,7 +1522,8 @@ /// /// - Parameter containerIdentifier: The identifier of the CloudKit container used to synchronize /// data. - public func attachMetadatabase(containerIdentifier: String) throws { + public func attachMetadatabase(containerIdentifier: String? = nil) throws { + // TODO let databasePath = try SQLQueryExpression( """ SELECT "file" FROM pragma_database_list() @@ -1539,6 +1569,7 @@ case invalidForeignKeyAction(ForeignKey) case invalidTableName(String) case metadatabaseMismatch(attachedPath: String, syncEngineConfiguredPath: String) + case noCloudKitContainer case nonNullColumnsWithoutDefault(tableName: String, columnNames: [String]) case triggersWithoutSynchronizationCheck([String]) case unknown diff --git a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md index 31573edb..42893c22 100644 --- a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md +++ b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md @@ -89,15 +89,11 @@ struct MyApp: App { ``` The `SyncEngine` - [initializer]() +[initializer]() has more options you may be interested in configuring. -> Important: A few important things to note about this: -> -> * The CloudKit container identifier must be explicitly provided and unfortunately cannot be -> extracted from Entitlements.plist automatically. That privilege is only afforded to SwiftData. -> * You must explicitly provide all tables that you want to synchronize. We do this so that you -> can have the option of having some local tables that are not synchronized to CloudKit. +> Important: You must explicitly provide all tables that you want to synchronize. We do this so that +> you can have the option of having some local tables that are not synchronized to CloudKit. Once this work is done the app should work exactly as it did before, but now any changes made to the database will be synchronized to CloudKit. You will still interact with your local SQLite diff --git a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md index 56ab876f..f4121aa0 100644 --- a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md +++ b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKitSharing.md @@ -391,7 +391,7 @@ struct MyApp: App { try! prepareDependencies { $0.defaultDatabase = try appDatabase() $0.defaultSyncEngine = try SyncEngine( - database: $0.defaultDatabase, + for: $0.defaultDatabase, tables: RemindersList.self, Reminder.self, privateTables: RemindersListPrivate.self ) From c92ea7187dae967a3ba3532b155fd0f4e3afa3a6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 4 Aug 2025 15:40:45 -0700 Subject: [PATCH 3/3] wip --- Examples/CloudKitPlayground/Schema.swift | 4 +--- Examples/Reminders/Schema.swift | 4 +--- .../SharingGRDBCore/CloudKit/SyncEngine.swift | 16 ++++++++++++++-- .../Documentation.docc/Articles/CloudKit.md | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Examples/CloudKitPlayground/Schema.swift b/Examples/CloudKitPlayground/Schema.swift index ab63de71..9b38d3f6 100644 --- a/Examples/CloudKitPlayground/Schema.swift +++ b/Examples/CloudKitPlayground/Schema.swift @@ -22,9 +22,7 @@ func appDatabase() throws -> any DatabaseWriter { let database: any DatabaseWriter var configuration = Configuration() configuration.prepareDatabase { db in - try db.attachMetadatabase( - containerIdentifier: "iCloud.co.pointfree.SQLiteData.demos.CloudKitPlayground" - ) + try db.attachMetadatabase() #if DEBUG db.trace(options: .profile) { if context == .live { diff --git a/Examples/Reminders/Schema.swift b/Examples/Reminders/Schema.swift index 1266c9de..9b6be2df 100644 --- a/Examples/Reminders/Schema.swift +++ b/Examples/Reminders/Schema.swift @@ -105,9 +105,7 @@ func appDatabase() throws -> any DatabaseWriter { let database: any DatabaseWriter var configuration = Configuration() configuration.prepareDatabase { db in - try db.attachMetadatabase( - containerIdentifier: "iCloud.co.pointfree.SQLiteData.demos.field-timestamps-2.Reminders" - ) + try db.attachMetadatabase() #if DEBUG db.trace(options: .profile) { if context == .live { diff --git a/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift b/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift index 6e7e810c..790b4f98 100644 --- a/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift +++ b/Sources/SharingGRDBCore/CloudKit/SyncEngine.swift @@ -1512,7 +1512,7 @@ /// func appDatabase() -> any DatabaseWriter { /// var configuration = Configuration() /// configuration.prepareDatabase = { db in - /// db.attachMetadatabase(containerIdentifier: "iCloud.my.company.MyApp") + /// db.attachMetadatabase() /// … /// } /// } @@ -1523,7 +1523,19 @@ /// - Parameter containerIdentifier: The identifier of the CloudKit container used to synchronize /// data. public func attachMetadatabase(containerIdentifier: String? = nil) throws { - // TODO + let containerIdentifier = containerIdentifier + ?? ModelConfiguration(groupContainer: .automatic).cloudKitContainerIdentifier + + guard let containerIdentifier else { + throw SyncEngine.SchemaError( + reason: .noCloudKitContainer, + debugDescription: """ + No default CloudKit container found. Please add a container identifier to your app's \ + entitlements. + """ + ) + } + let databasePath = try SQLQueryExpression( """ SELECT "file" FROM pragma_database_list() diff --git a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md index 42893c22..abaa1d20 100644 --- a/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md +++ b/Sources/SharingGRDBCore/Documentation.docc/Articles/CloudKit.md @@ -108,7 +108,7 @@ you can use the `prepareDatabase` method on `Configuration` to attach the metada func appDatabase() -> any DatabaseWriter { var configuration = Configuration() configuration.prepareDatabase = { db in - db.attachMetadatabase(containerIdentifier: "iCloud.my.company.MyApp") + db.attachMetadatabase() … } }