Issue #938

To let app and extension to talk to the same database, we need to use AppGroup. Here is how to use replacePersistentStore

Replaces one persistent store with another

actor DatabaseMigrator {
    @AppStorage("DatabaseMigrator.hasMigrated") var hasMigrated = false
    
    func migrateIfNeeded() {
        guard
            !hasMigrated
        else { return }
        
        migrate()
        
        hasMigrated = true
    }
    
    private func migrate() {
        let oldContainer = NSPersistentCloudKitContainer(name: "Bookmarks")
        
        guard
            let oldStoreUrl = oldContainer.persistentStoreDescriptions.first?.url,
            let newStoreUrl = Constants.appGroup.folderUrl?.appendingPathComponent(oldContainer.name + ".sqlite"),
            FileManager.default.fileExists(atPath: oldStoreUrl.path),
            !FileManager.default.fileExists(atPath: newStoreUrl.path)
        else { return }
        
        let coordinator = oldContainer.persistentStoreCoordinator
        do {
            try coordinator.replacePersistentStore(
                at: newStoreUrl,
                withPersistentStoreFrom: oldStoreUrl,
                type: .sqlite
            )
            
            try coordinator.destroyPersistentStore(at: oldStoreUrl, type: .sqlite)
        } catch {
            print(error)
        }
    }
}

There is a note on migratePersistentStore vs replacePersistentStore from Core Data WWDC 2020 FAQ

Additionally you should almost never use NSPersistentStoreCoordinator’s migratePersistentStore… method but instead use the newer replacePersistentStoreAtURL.. (you can replace emptiness to make a copy). The former loads the store into memory so you can do fairly radical things like write it out as a different store type. It pre-dates iOS. The latter will perform an APFS clone where possible.

Read more