From 76f7fb775b02cb2e8cd20fcd8259b47aa06ef09a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 19 Jul 2026 23:38:15 -0700 Subject: [PATCH 01/11] Add case studies for SwiftUI issue --- Examples/CaseStudies/ChildIdentityReset.swift | 105 ++++++++++++++++++ Examples/CaseStudies/ParentDrivenQuery.swift | 103 +++++++++++++++++ .../ParentRerenderAnimations.swift | 94 ++++++++++++++++ .../ParentRerenderCancellation.swift | 100 +++++++++++++++++ .../ParentRerenderDynamicQuery.swift | 104 +++++++++++++++++ .../CaseStudies/ParentRerenderLoadError.swift | 75 +++++++++++++ .../ParentRerenderLoadedData.swift | 100 +++++++++++++++++ 7 files changed, 681 insertions(+) create mode 100644 Examples/CaseStudies/ChildIdentityReset.swift create mode 100644 Examples/CaseStudies/ParentDrivenQuery.swift create mode 100644 Examples/CaseStudies/ParentRerenderAnimations.swift create mode 100644 Examples/CaseStudies/ParentRerenderCancellation.swift create mode 100644 Examples/CaseStudies/ParentRerenderDynamicQuery.swift create mode 100644 Examples/CaseStudies/ParentRerenderLoadError.swift create mode 100644 Examples/CaseStudies/ParentRerenderLoadedData.swift diff --git a/Examples/CaseStudies/ChildIdentityReset.swift b/Examples/CaseStudies/ChildIdentityReset.swift new file mode 100644 index 00000000..be7dc421 --- /dev/null +++ b/Examples/CaseStudies/ChildIdentityReset.swift @@ -0,0 +1,105 @@ +import SQLiteData +import SwiftUI + +struct ChildIdentityResetCaseStudy: SwiftUICaseStudy { + let readMe = """ + This demonstrates that the `@Fetch*` tools behave like `@State` when a view's identity \ + changes: their state survives re-renders of the parent view, but is discarded and rebuilt \ + when the parent changes the child view's identity with the `id` view modifier. + + Toggle "Favorites only" in the child view to load a filtered query, then tap "Reset child \ + identity". The child view is rebuilt from scratch: the toggle returns to its default and \ + the list returns to the unfiltered query, keeping the UI and data consistent. + """ + let caseStudyTitle = "Resetting child identity" + + @State private var resetCount = 0 + + var body: some View { + List { + Section { + Button("Reset child identity: \(resetCount)") { + resetCount += 1 + } + } + FactsListView() + .id(resetCount) + } + } +} + +private struct FactsListView: View { + @State private var isFavoritesOnly = false + @FetchAll(Fact.all) private var facts + + var body: some View { + Section { + Toggle("Favorites only", isOn: $isFavoritesOnly) + ForEach(facts) { fact in + HStack { + Text(fact.body) + Spacer() + if fact.isFavorite { + Image(systemName: "star.fill") + .foregroundStyle(.yellow) + } + } + } + } + .task(id: isFavoritesOnly) { + await withErrorReporting { + if isFavoritesOnly { + try await $facts.load(Fact.where(\.isFavorite)).task + } else { + try await $facts.load(Fact.all).task + } + } + } + } +} + +@Table +nonisolated private struct Fact: Identifiable { + let id: Int + var body: String + var isFavorite = false +} + +extension DatabaseWriter where Self == DatabaseQueue { + static var childIdentityResetDatabase: Self { + let databaseQueue = try! DatabaseQueue() + var migrator = DatabaseMigrator() + migrator.registerMigration("Create 'facts' table") { db in + try #sql( + """ + CREATE TABLE "facts" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "body" TEXT NOT NULL, + "isFavorite" INTEGER NOT NULL DEFAULT 0 + ) STRICT + """ + ) + .execute(db) + try Fact.insert { + Fact.Draft(body: "SQLite was first released in the year 2000.", isFavorite: true) + Fact.Draft(body: "SQLite is the most widely deployed database in the world.") + Fact.Draft(body: "SQLite is a C library, not a client-server database.", isFavorite: true) + Fact.Draft(body: "SQLite databases are a single file on disk.") + } + .execute(db) + } + try! migrator.migrate(databaseQueue) + return databaseQueue + } +} + +#Preview { + let _ = prepareDependencies { + $0.defaultDatabase = .childIdentityResetDatabase + } + NavigationStack { + CaseStudyView { + ChildIdentityResetCaseStudy() + } + } +} diff --git a/Examples/CaseStudies/ParentDrivenQuery.swift b/Examples/CaseStudies/ParentDrivenQuery.swift new file mode 100644 index 00000000..c2443398 --- /dev/null +++ b/Examples/CaseStudies/ParentDrivenQuery.swift @@ -0,0 +1,103 @@ +import SQLiteData +import SwiftUI + +struct ParentDrivenQueryCaseStudy: SwiftUICaseStudy { + let readMe = """ + This demonstrates how to drive a child view's query from parent state by constructing the \ + `@FetchAll` in the child's initializer, analogous to constructing a SwiftData `@Query` with \ + a dynamic predicate in a view's initializer. + + Toggling "Favorites only" re-initializes the child view with a different query, and the \ + child should immediately display the results of the new query. Tapping "Re-render parent" \ + re-initializes the child with the same query, which should have no effect. + """ + let caseStudyTitle = "Parent-driven queries" + + @State private var isFavoritesOnly = false + @State private var rerenderCount = 0 + + var body: some View { + List { + Section { + Toggle("Favorites only", isOn: $isFavoritesOnly) + Button("Re-render parent: \(rerenderCount)") { + rerenderCount += 1 + } + } + FactsListView(isFavoritesOnly: isFavoritesOnly) + } + } +} + +private struct FactsListView: View { + @FetchAll private var facts: [Fact] + + init(isFavoritesOnly: Bool) { + if isFavoritesOnly { + _facts = FetchAll(Fact.where(\.isFavorite)) + } else { + _facts = FetchAll(Fact.all) + } + } + + var body: some View { + Section { + ForEach(facts) { fact in + HStack { + Text(fact.body) + Spacer() + if fact.isFavorite { + Image(systemName: "star.fill") + .foregroundStyle(.yellow) + } + } + } + } + } +} + +@Table +nonisolated private struct Fact: Identifiable { + let id: Int + var body: String + var isFavorite = false +} + +extension DatabaseWriter where Self == DatabaseQueue { + static var parentDrivenQueryDatabase: Self { + let databaseQueue = try! DatabaseQueue() + var migrator = DatabaseMigrator() + migrator.registerMigration("Create 'facts' table") { db in + try #sql( + """ + CREATE TABLE "facts" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "body" TEXT NOT NULL, + "isFavorite" INTEGER NOT NULL DEFAULT 0 + ) STRICT + """ + ) + .execute(db) + try Fact.insert { + Fact.Draft(body: "SQLite was first released in the year 2000.", isFavorite: true) + Fact.Draft(body: "SQLite is the most widely deployed database in the world.") + Fact.Draft(body: "SQLite is a C library, not a client-server database.", isFavorite: true) + Fact.Draft(body: "SQLite databases are a single file on disk.") + } + .execute(db) + } + try! migrator.migrate(databaseQueue) + return databaseQueue + } +} + +#Preview { + let _ = prepareDependencies { + $0.defaultDatabase = .parentDrivenQueryDatabase + } + NavigationStack { + CaseStudyView { + ParentDrivenQueryCaseStudy() + } + } +} diff --git a/Examples/CaseStudies/ParentRerenderAnimations.swift b/Examples/CaseStudies/ParentRerenderAnimations.swift new file mode 100644 index 00000000..36301722 --- /dev/null +++ b/Examples/CaseStudies/ParentRerenderAnimations.swift @@ -0,0 +1,94 @@ +import SQLiteData +import SwiftUI + +struct ParentRerenderAnimationsCaseStudy: SwiftUICaseStudy { + let readMe = """ + This demonstrates that animations provided to the `@Fetch*` tools continue to work after a \ + parent view re-renders. + + The list below is loaded in the child view's `task` with an `animation` parameter, and so \ + tapping "Add fact" animates the new fact into the list. Tapping "Re-render parent" changes \ + `@State` in the parent view, which causes the child view (and its `@FetchAll`) to be \ + re-initialized. Adding a fact should continue to animate afterwards. + """ + let caseStudyTitle = "Animations with re-rendered parent" + + @State private var rerenderCount = 0 + @Dependency(\.defaultDatabase) var database + + var body: some View { + List { + Section { + Button("Re-render parent: \(rerenderCount)") { + rerenderCount += 1 + } + Button("Add fact") { + withErrorReporting { + try database.write { db in + try Fact.insert { + Fact.Draft(body: Date.now.formatted(date: .omitted, time: .standard)) + } + .execute(db) + } + } + } + } + FactsListView() + } + } +} + +private struct FactsListView: View { + @FetchAll(Fact.order { $0.id.desc() }) + private var facts + + var body: some View { + Section { + ForEach(facts) { fact in + Text(fact.body) + } + } + .task { + await withErrorReporting { + try await $facts.load(Fact.order { $0.id.desc() }, animation: .default).task + } + } + } +} + +@Table +nonisolated private struct Fact: Identifiable { + let id: Int + var body: String +} + +extension DatabaseWriter where Self == DatabaseQueue { + static var parentRerenderAnimationsDatabase: Self { + let databaseQueue = try! DatabaseQueue() + var migrator = DatabaseMigrator() + migrator.registerMigration("Create 'facts' table") { db in + try #sql( + """ + CREATE TABLE "facts" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "body" TEXT NOT NULL + ) STRICT + """ + ) + .execute(db) + } + try! migrator.migrate(databaseQueue) + return databaseQueue + } +} + +#Preview { + let _ = prepareDependencies { + $0.defaultDatabase = .parentRerenderAnimationsDatabase + } + NavigationStack { + CaseStudyView { + ParentRerenderAnimationsCaseStudy() + } + } +} diff --git a/Examples/CaseStudies/ParentRerenderCancellation.swift b/Examples/CaseStudies/ParentRerenderCancellation.swift new file mode 100644 index 00000000..8bacecac --- /dev/null +++ b/Examples/CaseStudies/ParentRerenderCancellation.swift @@ -0,0 +1,100 @@ +import SQLiteData +import SwiftUI + +struct ParentRerenderCancellationCaseStudy: SwiftUICaseStudy { + let readMe = """ + This demonstrates that a cancelled observation survives a parent view re-render. + + The child view below observes a list of facts that grows every second, and toggling "Live \ + updates" off cancels the observation using the subscription's `task`, freezing the list. \ + Tapping "Re-render parent" changes `@State` in the parent view, which causes the child view \ + (and its `@FetchAll`) to be re-initialized. The list should remain frozen, and should not \ + silently resume live updates while the toggle remains off. + """ + let caseStudyTitle = "Cancellation with re-rendered parent" + + @State private var rerenderCount = 0 + + var body: some View { + List { + Section { + Button("Re-render parent: \(rerenderCount)") { + rerenderCount += 1 + } + } + FactsListView() + } + } +} + +private struct FactsListView: View { + @State private var isLive = true + @FetchAll(Fact.order { $0.id.desc() }) + private var facts + @Dependency(\.defaultDatabase) var database + + var body: some View { + Section { + Toggle("Live updates", isOn: $isLive) + ForEach(facts) { fact in + Text(fact.body) + } + } + .task(id: isLive) { + guard isLive else { return } + await withErrorReporting { + try await $facts.load(Fact.order { $0.id.desc() }).task + } + } + .task { + do { + while true { + try await Task.sleep(for: .seconds(1)) + try await database.write { db in + try Fact.insert { + Fact.Draft(body: Date.now.formatted(date: .omitted, time: .standard)) + } + .execute(db) + } + } + } catch {} + } + } +} + +@Table +nonisolated private struct Fact: Identifiable { + let id: Int + var body: String +} + +extension DatabaseWriter where Self == DatabaseQueue { + static var parentRerenderCancellationDatabase: Self { + let databaseQueue = try! DatabaseQueue() + var migrator = DatabaseMigrator() + migrator.registerMigration("Create 'facts' table") { db in + try #sql( + """ + CREATE TABLE "facts" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "body" TEXT NOT NULL + ) STRICT + """ + ) + .execute(db) + } + try! migrator.migrate(databaseQueue) + return databaseQueue + } +} + +#Preview { + let _ = prepareDependencies { + $0.defaultDatabase = .parentRerenderCancellationDatabase + } + NavigationStack { + CaseStudyView { + ParentRerenderCancellationCaseStudy() + } + } +} diff --git a/Examples/CaseStudies/ParentRerenderDynamicQuery.swift b/Examples/CaseStudies/ParentRerenderDynamicQuery.swift new file mode 100644 index 00000000..3a2af0a9 --- /dev/null +++ b/Examples/CaseStudies/ParentRerenderDynamicQuery.swift @@ -0,0 +1,104 @@ +import SQLiteData +import SwiftUI + +struct ParentRerenderDynamicQueryCaseStudy: SwiftUICaseStudy { + let readMe = """ + This demonstrates that a dynamically loaded query survives a parent view re-render. + + The child view below starts with a query for all facts, and toggling "Favorites only" loads \ + a filtered query. Tapping "Re-render parent" changes `@State` in the parent view, which \ + causes the child view (and its `@FetchAll`) to be re-initialized. The filtered facts should \ + remain on screen, and should not silently revert to the unfiltered query while the toggle \ + remains on. + """ + let caseStudyTitle = "Dynamic queries with re-rendered parent" + + @State private var rerenderCount = 0 + + var body: some View { + List { + Section { + Button("Re-render parent: \(rerenderCount)") { + rerenderCount += 1 + } + } + FactsListView() + } + } +} + +private struct FactsListView: View { + @State private var isFavoritesOnly = false + @FetchAll(Fact.all) private var facts + + var body: some View { + Section { + Toggle("Favorites only", isOn: $isFavoritesOnly) + ForEach(facts) { fact in + HStack { + Text(fact.body) + Spacer() + if fact.isFavorite { + Image(systemName: "star.fill") + .foregroundStyle(.yellow) + } + } + } + } + .task(id: isFavoritesOnly) { + await withErrorReporting { + if isFavoritesOnly { + try await $facts.load(Fact.where(\.isFavorite)).task + } else { + try await $facts.load(Fact.all).task + } + } + } + } +} + +@Table +nonisolated private struct Fact: Identifiable { + let id: Int + var body: String + var isFavorite = false +} + +extension DatabaseWriter where Self == DatabaseQueue { + static var parentRerenderDynamicQueryDatabase: Self { + let databaseQueue = try! DatabaseQueue() + var migrator = DatabaseMigrator() + migrator.registerMigration("Create 'facts' table") { db in + try #sql( + """ + CREATE TABLE "facts" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "body" TEXT NOT NULL, + "isFavorite" INTEGER NOT NULL DEFAULT 0 + ) STRICT + """ + ) + .execute(db) + try Fact.insert { + Fact.Draft(body: "SQLite was first released in the year 2000.", isFavorite: true) + Fact.Draft(body: "SQLite is the most widely deployed database in the world.") + Fact.Draft(body: "SQLite is a C library, not a client-server database.", isFavorite: true) + Fact.Draft(body: "SQLite databases are a single file on disk.") + } + .execute(db) + } + try! migrator.migrate(databaseQueue) + return databaseQueue + } +} + +#Preview { + let _ = prepareDependencies { + $0.defaultDatabase = .parentRerenderDynamicQueryDatabase + } + NavigationStack { + CaseStudyView { + ParentRerenderDynamicQueryCaseStudy() + } + } +} diff --git a/Examples/CaseStudies/ParentRerenderLoadError.swift b/Examples/CaseStudies/ParentRerenderLoadError.swift new file mode 100644 index 00000000..1704cb60 --- /dev/null +++ b/Examples/CaseStudies/ParentRerenderLoadError.swift @@ -0,0 +1,75 @@ +import GRDB +import SQLiteData +import SwiftUI + +struct ParentRerenderLoadErrorCaseStudy: SwiftUICaseStudy { + let readMe = """ + This demonstrates that a load error survives a parent view re-render. + + The child view below loads a query that always fails, and so it renders the `loadError` of \ + its `@Fetch` property. Tapping the stepper changes `@State` in the parent view, which causes \ + the child view (and its `@Fetch`) to be re-initialized. The error should remain on screen, \ + and should not be silently discarded, which would make the view appear healthy even though \ + its query failed. + """ + let caseStudyTitle = "Load errors with re-rendered parent" + + @State private var count = 0 + + var body: some View { + List { + Section { + Stepper("Parent state: \(count)", value: $count) + } + FactsView(count: count) + } + } +} + +private struct FactsView: View { + let count: Int + @Fetch private var facts = Facts.Value() + + var body: some View { + Section("Facts (parent state: \(count))") { + if let loadError = $facts.loadError { + Label(loadError.localizedDescription, systemImage: "exclamationmark.triangle") + .foregroundStyle(.red) + } else { + Text("Facts: \(facts.count)") + } + } + .task { + try? await $facts.load(Facts()) + } + } + + private struct Facts: FetchKeyRequest { + struct Value { + var count = 0 + } + func fetch(_ db: Database) throws -> Value { + struct QueryFailure: LocalizedError { + var errorDescription: String? { "Something went wrong." } + } + throw QueryFailure() + } + } +} + +extension DatabaseWriter where Self == DatabaseQueue { + static var parentRerenderLoadErrorDatabase: Self { + try! DatabaseQueue() + } +} + +#Preview { + let _ = prepareDependencies { + $0.defaultDatabase = .parentRerenderLoadErrorDatabase + } + NavigationStack { + CaseStudyView { + ParentRerenderLoadErrorCaseStudy() + } + } +} diff --git a/Examples/CaseStudies/ParentRerenderLoadedData.swift b/Examples/CaseStudies/ParentRerenderLoadedData.swift new file mode 100644 index 00000000..1c689c0f --- /dev/null +++ b/Examples/CaseStudies/ParentRerenderLoadedData.swift @@ -0,0 +1,100 @@ +import GRDB +import SQLiteData +import SwiftUI + +struct ParentRerenderLoadedDataCaseStudy: SwiftUICaseStudy { + let readMe = """ + This demonstrates that data loaded by the `@Fetch*` tools survives a parent view re-render. + + The child view below has a `@Fetch` property that begins with an empty default value and is \ + loaded in the view's `task`. Tapping the stepper changes `@State` in the parent view, which \ + causes the child view (and its `@Fetch`) to be re-initialized. The facts should remain on \ + screen, and should not revert to the empty default value. The same is true when any other \ + dynamic property in the parent changes, such as `@Environment`. + """ + let caseStudyTitle = "Loaded data with re-rendered parent" + + @State private var count = 0 + + var body: some View { + List { + Section { + Stepper("Parent state: \(count)", value: $count) + } + FactsListView(count: count) + } + } +} + +private struct FactsListView: View { + let count: Int + @Fetch private var facts = Facts.Value() + + var body: some View { + Section("Facts (parent state: \(count))") { + if facts.facts.isEmpty { + Text("No facts loaded") + } + ForEach(facts.facts) { fact in + Text(fact.body) + } + } + .task { + await withErrorReporting { + try await $facts.load(Facts()).task + } + } + } + + private struct Facts: FetchKeyRequest { + struct Value { + var facts: [Fact] = [] + } + func fetch(_ db: Database) throws -> Value { + try Value(facts: Fact.order { $0.id.desc() }.fetchAll(db)) + } + } +} + +@Table +nonisolated private struct Fact: Identifiable { + let id: Int + var body: String +} + +extension DatabaseWriter where Self == DatabaseQueue { + static var parentRerenderLoadedDataDatabase: Self { + let databaseQueue = try! DatabaseQueue() + var migrator = DatabaseMigrator() + migrator.registerMigration("Create 'facts' table") { db in + try #sql( + """ + CREATE TABLE "facts" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "body" TEXT NOT NULL + ) STRICT + """ + ) + .execute(db) + try Fact.insert { + Fact.Draft(body: "SQLite was first released in the year 2000.") + Fact.Draft(body: "SQLite is the most widely deployed database in the world.") + Fact.Draft(body: "SQLite is a C library, not a client-server database.") + } + .execute(db) + } + try! migrator.migrate(databaseQueue) + return databaseQueue + } +} + +#Preview { + let _ = prepareDependencies { + $0.defaultDatabase = .parentRerenderLoadedDataDatabase + } + NavigationStack { + CaseStudyView { + ParentRerenderLoadedDataCaseStudy() + } + } +} From 826120b15c62bd94c953554f907dfbe43957f615 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 19 Jul 2026 23:49:36 -0700 Subject: [PATCH 02/11] @State fix --- Sources/SQLiteData/Fetch.swift | 27 ++++++++++++++++++++++----- Sources/SQLiteData/FetchAll.swift | 27 ++++++++++++++++++++++----- Sources/SQLiteData/FetchOne.swift | 27 ++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/Sources/SQLiteData/Fetch.swift b/Sources/SQLiteData/Fetch.swift index ea645873..29a4cecb 100644 --- a/Sources/SQLiteData/Fetch.swift +++ b/Sources/SQLiteData/Fetch.swift @@ -22,11 +22,28 @@ public import Sharing @dynamicMemberLookup @propertyWrapper public struct Fetch: Sendable { - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) package, - /// a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader + #if canImport(SwiftUI) + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) + /// package, a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader { + @storageRestrictions(initializes: state) + init(initialValue) { + state = SwiftUI.State(wrappedValue: initialValue) + } + get { state.wrappedValue } + nonmutating set { state.wrappedValue = newValue } + } + + private let state: SwiftUI.State> + #else + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) + /// package, a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader + #endif /// Data associated with the underlying query. public var wrappedValue: Value { diff --git a/Sources/SQLiteData/FetchAll.swift b/Sources/SQLiteData/FetchAll.swift index ec118cdf..6569f606 100644 --- a/Sources/SQLiteData/FetchAll.swift +++ b/Sources/SQLiteData/FetchAll.swift @@ -22,11 +22,28 @@ public import Sharing @dynamicMemberLookup @propertyWrapper public struct FetchAll: Sendable { - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) package, - /// a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader<[Element]> = SharedReader(value: []) + #if canImport(SwiftUI) + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) + /// package, a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader<[Element]> { + @storageRestrictions(initializes: state) + init(initialValue) { + state = SwiftUI.State(wrappedValue: initialValue) + } + get { state.wrappedValue } + nonmutating set { state.wrappedValue = newValue } + } + + private let state: SwiftUI.State> + #else + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) + /// package, a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader<[Element]> = SharedReader(value: []) + #endif /// A collection of data associated with the underlying query. public var wrappedValue: [Element] { diff --git a/Sources/SQLiteData/FetchOne.swift b/Sources/SQLiteData/FetchOne.swift index 4e6b799d..01240a4c 100644 --- a/Sources/SQLiteData/FetchOne.swift +++ b/Sources/SQLiteData/FetchOne.swift @@ -21,11 +21,28 @@ public import StructuredQueriesCore @dynamicMemberLookup @propertyWrapper public struct FetchOne: Sendable { - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) package, - /// a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader + #if canImport(SwiftUI) + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) + /// package, a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader { + @storageRestrictions(initializes: state) + init(initialValue) { + state = SwiftUI.State(wrappedValue: initialValue) + } + get { state.wrappedValue } + nonmutating set { state.wrappedValue = newValue } + } + + private let state: SwiftUI.State> + #else + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) + /// package, a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader + #endif /// A value associated with the underlying query. public var wrappedValue: Value { From 57c9140c1ed6567ef75d539d79e8cdff937dfc9e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 20 Jul 2026 08:55:10 -0700 Subject: [PATCH 03/11] FetchBox --- Sources/SQLiteData/Fetch.swift | 41 +++-- Sources/SQLiteData/FetchAll.swift | 84 +++++++---- Sources/SQLiteData/FetchOne.swift | 168 +++++++++++++-------- Sources/SQLiteData/Internal/FetchBox.swift | 32 ++++ Tests/SQLiteDataTests/FetchBoxTests.swift | 78 ++++++++++ 5 files changed, 302 insertions(+), 101 deletions(-) create mode 100644 Sources/SQLiteData/Internal/FetchBox.swift create mode 100644 Tests/SQLiteDataTests/FetchBoxTests.swift diff --git a/Sources/SQLiteData/Fetch.swift b/Sources/SQLiteData/Fetch.swift index 29a4cecb..2a7c5516 100644 --- a/Sources/SQLiteData/Fetch.swift +++ b/Sources/SQLiteData/Fetch.swift @@ -1,7 +1,6 @@ public import GRDB public import Sharing import StructuredQueriesCore -public import Sharing #if canImport(Combine) public import Combine @@ -27,22 +26,26 @@ public struct Fetch: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader { - @storageRestrictions(initializes: state) + public private(set) var sharedReader: SharedReader { + @storageRestrictions(initializes: box, state) init(initialValue) { - state = SwiftUI.State(wrappedValue: initialValue) + let box = FetchBox(sharedReader: initialValue) + self.box = box + state = SwiftUI.State(wrappedValue: box) } - get { state.wrappedValue } - nonmutating set { state.wrappedValue = newValue } + get { state.wrappedValue.sharedReader } + nonmutating set { state.wrappedValue.sharedReader = newValue } } - private let state: SwiftUI.State> + private let box: FetchBox + private let state: SwiftUI.State> + private let generation = SwiftUI.State(wrappedValue: 0) #else /// The underlying shared reader powering the property wrapper. /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader + public private(set) var sharedReader: SharedReader #endif /// Data associated with the underlying query. @@ -110,6 +113,7 @@ public struct Fetch: Sendable { database: (any DatabaseReader)? = nil ) { sharedReader = SharedReader(wrappedValue: wrappedValue, .fetch(request, database: database)) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Replaces the wrapped value with data from the given request. @@ -127,6 +131,19 @@ public struct Fetch: Sendable { try await sharedReader.load(.fetch(request, database: database)) return FetchSubscription(sharedReader: sharedReader) } + + #if !canImport(SwiftUI) + @_transparent + #endif + private func setFetchKeyID( + for request: some FetchKeyRequest, + database: (any DatabaseReader)?, + scheduler: (any ValueObservationScheduler & Hashable)? + ) { + #if canImport(SwiftUI) + box.fetchKeyID = FetchKey(request: request, database: database, scheduler: scheduler).id + #endif + } } extension Fetch { @@ -149,6 +166,7 @@ extension Fetch { wrappedValue: wrappedValue, .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Replaces the wrapped value with data from the given request. @@ -186,7 +204,11 @@ extension Fetch: Equatable where Value: Equatable { #if canImport(SwiftUI) extension Fetch: DynamicProperty { public func update() { - sharedReader.update() + let persisted = state.wrappedValue + if persisted !== box { + persisted.update(from: box) + } + persisted.subscribe(generation: generation) } /// Initializes this property with a request associated with the wrapped value. @@ -209,6 +231,7 @@ extension Fetch: Equatable where Value: Equatable { wrappedValue: wrappedValue, .fetch(request, database: database, animation: animation) ) + setFetchKeyID(for: request, database: database, scheduler: .animation(animation)) } /// Replaces the wrapped value with data from the given request. diff --git a/Sources/SQLiteData/FetchAll.swift b/Sources/SQLiteData/FetchAll.swift index 6569f606..e2a9fd3f 100644 --- a/Sources/SQLiteData/FetchAll.swift +++ b/Sources/SQLiteData/FetchAll.swift @@ -1,7 +1,6 @@ public import GRDB public import Sharing public import StructuredQueriesCore -public import Sharing #if canImport(Combine) public import Combine @@ -27,22 +26,26 @@ public struct FetchAll: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader<[Element]> { - @storageRestrictions(initializes: state) + public private(set) var sharedReader: SharedReader<[Element]> { + @storageRestrictions(initializes: box, state) init(initialValue) { - state = SwiftUI.State(wrappedValue: initialValue) + let box = FetchBox(sharedReader: initialValue) + self.box = box + state = SwiftUI.State(wrappedValue: box) } - get { state.wrappedValue } - nonmutating set { state.wrappedValue = newValue } + get { state.wrappedValue.sharedReader } + nonmutating set { state.wrappedValue.sharedReader = newValue } } - private let state: SwiftUI.State> + private let box: FetchBox<[Element]> + private let state: SwiftUI.State> + private let generation = SwiftUI.State(wrappedValue: 0) #else /// The underlying shared reader powering the property wrapper. /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader<[Element]> = SharedReader(value: []) + public private(set) var sharedReader: SharedReader<[Element]> = SharedReader(value: []) #endif /// A collection of data associated with the underlying query. @@ -156,13 +159,12 @@ public struct FetchAll: Sendable { Element == V.QueryOutput, V.QueryOutput: Sendable { + let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchAllStatementValueRequest(statement: statement), - database: database - ) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -181,13 +183,12 @@ public struct FetchAll: Sendable { Element: QueryRepresentable, Element == S.QueryValue.QueryOutput { + let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchAllStatementValueRequest(statement: statement), - database: database - ) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Replaces the wrapped value with data from the given query. @@ -236,10 +237,29 @@ public struct FetchAll: Sendable { ) return FetchSubscription(sharedReader: sharedReader) } + + #if !canImport(SwiftUI) + @_transparent + #endif + private func setFetchKeyID( + for request: some FetchKeyRequest, + database: (any DatabaseReader)?, + scheduler: (any ValueObservationScheduler & Hashable)? + ) { + #if canImport(SwiftUI) + box.fetchKeyID = FetchKey(request: request, database: database, scheduler: scheduler).id + #endif + } } extension FetchAll { - @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") + @available( + *, + deprecated, + message: """ + '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. + """ + ) public init( wrappedValue: [Element] = [], database: (any DatabaseReader)? = nil, @@ -311,14 +331,12 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { + let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchAllStatementValueRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -340,14 +358,12 @@ extension FetchAll { Element: QueryRepresentable, Element == S.QueryValue.QueryOutput { + let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchAllStatementValueRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Replaces the wrapped value with data from the given query. @@ -420,10 +436,20 @@ extension FetchAll: Equatable where Element: Equatable { #if canImport(SwiftUI) extension FetchAll: DynamicProperty { public func update() { - sharedReader.update() + let persisted = state.wrappedValue + if persisted !== box { + persisted.update(from: box) + } + persisted.subscribe(generation: generation) } - @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") + @available( + *, + deprecated, + message: """ + '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. + """ + ) public init( wrappedValue: [Element] = [], database: (any DatabaseReader)? = nil, diff --git a/Sources/SQLiteData/FetchOne.swift b/Sources/SQLiteData/FetchOne.swift index 01240a4c..39f3ba88 100644 --- a/Sources/SQLiteData/FetchOne.swift +++ b/Sources/SQLiteData/FetchOne.swift @@ -26,22 +26,26 @@ public struct FetchOne: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader { - @storageRestrictions(initializes: state) + public private(set) var sharedReader: SharedReader { + @storageRestrictions(initializes: box, state) init(initialValue) { - state = SwiftUI.State(wrappedValue: initialValue) + let box = FetchBox(sharedReader: initialValue) + self.box = box + state = SwiftUI.State(wrappedValue: box) } - get { state.wrappedValue } - nonmutating set { state.wrappedValue = newValue } + get { state.wrappedValue.sharedReader } + nonmutating set { state.wrappedValue.sharedReader = newValue } } - private let state: SwiftUI.State> + private let box: FetchBox + private let state: SwiftUI.State> + private let generation = SwiftUI.State(wrappedValue: 0) #else /// The underlying shared reader powering the property wrapper. /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public var sharedReader: SharedReader + public private(set) var sharedReader: SharedReader #endif /// A value associated with the underlying query. @@ -123,10 +127,12 @@ public struct FetchOne: Sendable { Value: StructuredQueriesCore.Table & QueryRepresentable, Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) + let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(FetchOneStatementValueRequest(statement: statement), database: database) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query that fetches the first row from a table. @@ -145,10 +151,12 @@ public struct FetchOne: Sendable { Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) + let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(FetchOneStatementOptionalProtocolRequest(statement: statement), database: database) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -187,10 +195,12 @@ public struct FetchOne: Sendable { where Value == V.QueryOutput { + let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(FetchOneStatementValueRequest(statement: statement), database: database) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -208,10 +218,12 @@ public struct FetchOne: Sendable { where Value == V.QueryOutput? { + let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(FetchOneStatementOptionalValueRequest(statement: statement), database: database) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -230,10 +242,12 @@ public struct FetchOne: Sendable { Value: QueryRepresentable, Value == S.QueryValue.QueryOutput { + let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(FetchOneStatementValueRequest(statement: statement), database: database) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with an optional value. @@ -255,10 +269,12 @@ public struct FetchOne: Sendable { S.Joins == () { let statement = statement.selectStar().asSelect().limit(1) + let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(FetchOneStatementOptionalValueRequest(statement: statement), database: database) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with an optional value. @@ -279,13 +295,12 @@ public struct FetchOne: Sendable { S.QueryValue: StructuredQueriesCore._OptionalProtocol, Value == S.QueryValue.QueryOutput { + let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementOptionalProtocolRequest(statement: statement), - database: database - ) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with an optional value. @@ -305,10 +320,12 @@ public struct FetchOne: Sendable { Value: StructuredQueriesCore._OptionalProtocol, Value.QueryOutput == Value { + let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(FetchOneStatementOptionalProtocolRequest(statement: statement), database: database) + .fetch(request, database: database) ) + setFetchKeyID(for: request, database: database, scheduler: nil) } /// Replaces the wrapped value with data from the given query. @@ -445,10 +462,29 @@ public struct FetchOne: Sendable { ) return FetchSubscription(sharedReader: sharedReader) } + + #if !canImport(SwiftUI) + @_transparent + #endif + private func setFetchKeyID( + for request: some FetchKeyRequest, + database: (any DatabaseReader)?, + scheduler: (any ValueObservationScheduler & Hashable)? + ) { + #if canImport(SwiftUI) + box.fetchKeyID = FetchKey(request: request, database: database, scheduler: scheduler).id + #endif + } } extension FetchOne { - @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") + @available( + *, + deprecated, + message: """ + '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. + """ + ) public init( wrappedValue: sending Value, database: (any DatabaseReader)? = nil, @@ -461,7 +497,13 @@ extension FetchOne { sharedReader = SharedReader(value: wrappedValue) } - @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") + @available( + *, + deprecated, + message: """ + '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. + """ + ) public init( wrappedValue: sending Value = Value._none, database: (any DatabaseReader)? = nil, @@ -492,14 +534,12 @@ extension FetchOne { Value: StructuredQueriesCore.Table & QueryRepresentable, Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) + let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementValueRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query that fetches the first row from a table. @@ -521,14 +561,12 @@ extension FetchOne { Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) + let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementOptionalProtocolRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -573,14 +611,12 @@ extension FetchOne { where Value == V.QueryOutput { + let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementValueRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -601,14 +637,12 @@ extension FetchOne { where Value == V.QueryOutput? { + let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementOptionalValueRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -630,14 +664,12 @@ extension FetchOne { Value: QueryRepresentable, Value == S.QueryValue.QueryOutput { + let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementValueRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with an optional value. @@ -662,14 +694,12 @@ extension FetchOne { S.Joins == () { let statement = statement.selectStar().asSelect().limit(1) + let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementOptionalValueRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with an optional value. @@ -693,14 +723,12 @@ extension FetchOne { S.QueryValue: StructuredQueriesCore._OptionalProtocol, Value == S.QueryValue.QueryOutput { + let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementOptionalProtocolRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with an optional value. @@ -723,14 +751,12 @@ extension FetchOne { Value: StructuredQueriesCore._OptionalProtocol, Value.QueryOutput == Value { + let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch( - FetchOneStatementOptionalProtocolRequest(statement: statement), - database: database, - scheduler: scheduler - ) + .fetch(request, database: database, scheduler: scheduler) ) + setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Replaces the wrapped value with data from the given query. @@ -922,10 +948,20 @@ extension FetchOne: Equatable where Value: Equatable { #if canImport(SwiftUI) extension FetchOne: DynamicProperty { public func update() { - sharedReader.update() + let persisted = state.wrappedValue + if persisted !== box { + persisted.update(from: box) + } + persisted.subscribe(generation: generation) } - @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") + @available( + *, + deprecated, + message: """ + '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. + """ + ) public init( wrappedValue: sending Value, database: (any DatabaseReader)? = nil, @@ -938,7 +974,13 @@ extension FetchOne: Equatable where Value: Equatable { sharedReader = SharedReader(value: wrappedValue) } - @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") + @available( + *, + deprecated, + message: """ + '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. + """ + ) public init( wrappedValue: sending Value = Value._none, database: (any DatabaseReader)? = nil, diff --git a/Sources/SQLiteData/Internal/FetchBox.swift b/Sources/SQLiteData/Internal/FetchBox.swift new file mode 100644 index 00000000..e0d64073 --- /dev/null +++ b/Sources/SQLiteData/Internal/FetchBox.swift @@ -0,0 +1,32 @@ +#if canImport(SwiftUI) + import Combine + import Sharing + import SwiftUI + + final class FetchBox: @unchecked Sendable { + var sharedReader: SharedReader + var fetchKeyID: FetchKeyID? + private var swiftUICancellable: AnyCancellable? + + init(sharedReader: SharedReader) { + self.sharedReader = sharedReader + } + + func update(from other: FetchBox) { + guard + let otherFetchKeyID = other.fetchKeyID, + otherFetchKeyID != fetchKeyID + else { return } + sharedReader = other.sharedReader + fetchKeyID = other.fetchKeyID + } + + func subscribe(generation: SwiftUI.State) { + guard #unavailable(iOS 17, macOS 14, tvOS 17, watchOS 10) else { return } + _ = generation.wrappedValue + swiftUICancellable = sharedReader.publisher + .dropFirst() + .sink { _ in generation.wrappedValue &+= 1 } + } + } +#endif diff --git a/Tests/SQLiteDataTests/FetchBoxTests.swift b/Tests/SQLiteDataTests/FetchBoxTests.swift new file mode 100644 index 00000000..c4016cfd --- /dev/null +++ b/Tests/SQLiteDataTests/FetchBoxTests.swift @@ -0,0 +1,78 @@ +#if canImport(SwiftUI) + import GRDB + import Sharing + import Testing + + @testable import SQLiteData + + @Suite struct FetchBoxTests { + let database: any DatabaseReader + + init() throws { + database = try DatabaseQueue() + } + + @Test func keyedReinitializationWithNewQueryIsAdopted() { + let persisted = FetchBox(sharedReader: SharedReader(value: 1)) + persisted.fetchKeyID = fetchKeyID(TestRequest(id: 1)) + let fresh = FetchBox(sharedReader: SharedReader(value: 2)) + fresh.fetchKeyID = fetchKeyID(TestRequest(id: 2)) + persisted.update(from: fresh) + #expect(persisted.sharedReader.wrappedValue == 2) + #expect(persisted.fetchKeyID == fresh.fetchKeyID) + } + + @Test func keyedReinitializationWithSameQueryIsIgnored() { + let persisted = FetchBox(sharedReader: SharedReader(value: 1)) + persisted.fetchKeyID = fetchKeyID(TestRequest(id: 1)) + let fresh = FetchBox(sharedReader: SharedReader(value: 2)) + fresh.fetchKeyID = fetchKeyID(TestRequest(id: 1)) + persisted.update(from: fresh) + #expect(persisted.sharedReader.wrappedValue == 1) + } + + @Test func keyedToKeylessReinitializationIsIgnored() { + let persisted = FetchBox(sharedReader: SharedReader(value: 1)) + persisted.fetchKeyID = fetchKeyID(TestRequest(id: 1)) + let fresh = FetchBox(sharedReader: SharedReader(value: 2)) + persisted.update(from: fresh) + #expect(persisted.sharedReader.wrappedValue == 1) + #expect(persisted.fetchKeyID != nil) + } + + @Test func keylessReinitializationIsIgnored() { + let persisted = FetchBox(sharedReader: SharedReader(value: 1)) + let fresh = FetchBox(sharedReader: SharedReader(value: 2)) + persisted.update(from: fresh) + #expect(persisted.sharedReader.wrappedValue == 1) + } + + @Test func keylessToKeyedReinitializationIsAdopted() { + let persisted = FetchBox(sharedReader: SharedReader(value: 1)) + let fresh = FetchBox(sharedReader: SharedReader(value: 2)) + fresh.fetchKeyID = fetchKeyID(TestRequest(id: 2)) + persisted.update(from: fresh) + #expect(persisted.sharedReader.wrappedValue == 2) + #expect(persisted.fetchKeyID == fresh.fetchKeyID) + } + + @Test func keylessReinitializationAfterLocalLoadIsIgnored() { + let persisted = FetchBox(sharedReader: SharedReader(value: [Int]())) + persisted.sharedReader = SharedReader(value: [1, 2, 3]) + let fresh = FetchBox(sharedReader: SharedReader(value: [Int]())) + persisted.update(from: fresh) + #expect(persisted.sharedReader.wrappedValue == [1, 2, 3]) + } + + private func fetchKeyID(_ request: some FetchKeyRequest) -> FetchKeyID { + FetchKey(request: request, database: database, scheduler: nil).id + } + } + + private struct TestRequest: FetchKeyRequest, Hashable { + let id: Int + func fetch(_ db: Database) throws -> Int { + id + } + } +#endif From f6a17e1145df841af15bf88155c3eeb2114b283a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 20 Jul 2026 17:20:09 -0700 Subject: [PATCH 04/11] wip --- .../ChildIdentityReset.swift | 0 .../ParentDrivenQuery.swift | 8 ++-- .../ParentRerenderAnimations.swift | 0 .../ParentRerenderCancellation.swift | 0 .../ParentRerenderDynamicQuery.swift | 0 .../ParentRerenderLoadError.swift | 2 +- .../ParentRerenderLoadedData.swift | 0 Sources/SQLiteData/Fetch.swift | 5 +-- Sources/SQLiteData/FetchAll.swift | 5 +-- Sources/SQLiteData/FetchOne.swift | 5 +-- Sources/SQLiteData/Internal/FetchBox.swift | 40 +++++++++++++------ Tests/SQLiteDataTests/FetchBoxTests.swift | 2 +- 12 files changed, 39 insertions(+), 28 deletions(-) rename Examples/CaseStudies/{ => Regression Coverage}/ChildIdentityReset.swift (100%) rename Examples/CaseStudies/{ => Regression Coverage}/ParentDrivenQuery.swift (92%) rename Examples/CaseStudies/{ => Regression Coverage}/ParentRerenderAnimations.swift (100%) rename Examples/CaseStudies/{ => Regression Coverage}/ParentRerenderCancellation.swift (100%) rename Examples/CaseStudies/{ => Regression Coverage}/ParentRerenderDynamicQuery.swift (100%) rename Examples/CaseStudies/{ => Regression Coverage}/ParentRerenderLoadError.swift (97%) rename Examples/CaseStudies/{ => Regression Coverage}/ParentRerenderLoadedData.swift (100%) diff --git a/Examples/CaseStudies/ChildIdentityReset.swift b/Examples/CaseStudies/Regression Coverage/ChildIdentityReset.swift similarity index 100% rename from Examples/CaseStudies/ChildIdentityReset.swift rename to Examples/CaseStudies/Regression Coverage/ChildIdentityReset.swift diff --git a/Examples/CaseStudies/ParentDrivenQuery.swift b/Examples/CaseStudies/Regression Coverage/ParentDrivenQuery.swift similarity index 92% rename from Examples/CaseStudies/ParentDrivenQuery.swift rename to Examples/CaseStudies/Regression Coverage/ParentDrivenQuery.swift index c2443398..b0a4d337 100644 --- a/Examples/CaseStudies/ParentDrivenQuery.swift +++ b/Examples/CaseStudies/Regression Coverage/ParentDrivenQuery.swift @@ -4,11 +4,11 @@ import SwiftUI struct ParentDrivenQueryCaseStudy: SwiftUICaseStudy { let readMe = """ This demonstrates how to drive a child view's query from parent state by constructing the \ - `@FetchAll` in the child's initializer, analogous to constructing a SwiftData `@Query` with \ - a dynamic predicate in a view's initializer. + `@FetchAll` in the child's initializer, analogous to constructing a SwiftData `@Query` with a \ + dynamic predicate in a view's initializer. - Toggling "Favorites only" re-initializes the child view with a different query, and the \ - child should immediately display the results of the new query. Tapping "Re-render parent" \ + Toggling "Favorites only" re-initializes the child view with a different query, and the child \ + should immediately display the results of the new query. Tapping "Re-render parent" \ re-initializes the child with the same query, which should have no effect. """ let caseStudyTitle = "Parent-driven queries" diff --git a/Examples/CaseStudies/ParentRerenderAnimations.swift b/Examples/CaseStudies/Regression Coverage/ParentRerenderAnimations.swift similarity index 100% rename from Examples/CaseStudies/ParentRerenderAnimations.swift rename to Examples/CaseStudies/Regression Coverage/ParentRerenderAnimations.swift diff --git a/Examples/CaseStudies/ParentRerenderCancellation.swift b/Examples/CaseStudies/Regression Coverage/ParentRerenderCancellation.swift similarity index 100% rename from Examples/CaseStudies/ParentRerenderCancellation.swift rename to Examples/CaseStudies/Regression Coverage/ParentRerenderCancellation.swift diff --git a/Examples/CaseStudies/ParentRerenderDynamicQuery.swift b/Examples/CaseStudies/Regression Coverage/ParentRerenderDynamicQuery.swift similarity index 100% rename from Examples/CaseStudies/ParentRerenderDynamicQuery.swift rename to Examples/CaseStudies/Regression Coverage/ParentRerenderDynamicQuery.swift diff --git a/Examples/CaseStudies/ParentRerenderLoadError.swift b/Examples/CaseStudies/Regression Coverage/ParentRerenderLoadError.swift similarity index 97% rename from Examples/CaseStudies/ParentRerenderLoadError.swift rename to Examples/CaseStudies/Regression Coverage/ParentRerenderLoadError.swift index 1704cb60..b156819a 100644 --- a/Examples/CaseStudies/ParentRerenderLoadError.swift +++ b/Examples/CaseStudies/Regression Coverage/ParentRerenderLoadError.swift @@ -40,7 +40,7 @@ private struct FactsView: View { } } .task { - try? await $facts.load(Facts()) + _ = try? await $facts.load(Facts()) } } diff --git a/Examples/CaseStudies/ParentRerenderLoadedData.swift b/Examples/CaseStudies/Regression Coverage/ParentRerenderLoadedData.swift similarity index 100% rename from Examples/CaseStudies/ParentRerenderLoadedData.swift rename to Examples/CaseStudies/Regression Coverage/ParentRerenderLoadedData.swift diff --git a/Sources/SQLiteData/Fetch.swift b/Sources/SQLiteData/Fetch.swift index 2a7c5516..ee36aa4a 100644 --- a/Sources/SQLiteData/Fetch.swift +++ b/Sources/SQLiteData/Fetch.swift @@ -26,7 +26,7 @@ public struct Fetch: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader { + public var sharedReader: SharedReader { @storageRestrictions(initializes: box, state) init(initialValue) { let box = FetchBox(sharedReader: initialValue) @@ -34,7 +34,6 @@ public struct Fetch: Sendable { state = SwiftUI.State(wrappedValue: box) } get { state.wrappedValue.sharedReader } - nonmutating set { state.wrappedValue.sharedReader = newValue } } private let box: FetchBox @@ -45,7 +44,7 @@ public struct Fetch: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader + public let sharedReader: SharedReader #endif /// Data associated with the underlying query. diff --git a/Sources/SQLiteData/FetchAll.swift b/Sources/SQLiteData/FetchAll.swift index e2a9fd3f..a75863a6 100644 --- a/Sources/SQLiteData/FetchAll.swift +++ b/Sources/SQLiteData/FetchAll.swift @@ -26,7 +26,7 @@ public struct FetchAll: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader<[Element]> { + public var sharedReader: SharedReader<[Element]> { @storageRestrictions(initializes: box, state) init(initialValue) { let box = FetchBox(sharedReader: initialValue) @@ -34,7 +34,6 @@ public struct FetchAll: Sendable { state = SwiftUI.State(wrappedValue: box) } get { state.wrappedValue.sharedReader } - nonmutating set { state.wrappedValue.sharedReader = newValue } } private let box: FetchBox<[Element]> @@ -45,7 +44,7 @@ public struct FetchAll: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader<[Element]> = SharedReader(value: []) + public let sharedReader: SharedReader<[Element]> #endif /// A collection of data associated with the underlying query. diff --git a/Sources/SQLiteData/FetchOne.swift b/Sources/SQLiteData/FetchOne.swift index 39f3ba88..9ec6400e 100644 --- a/Sources/SQLiteData/FetchOne.swift +++ b/Sources/SQLiteData/FetchOne.swift @@ -26,7 +26,7 @@ public struct FetchOne: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader { + public var sharedReader: SharedReader { @storageRestrictions(initializes: box, state) init(initialValue) { let box = FetchBox(sharedReader: initialValue) @@ -34,7 +34,6 @@ public struct FetchOne: Sendable { state = SwiftUI.State(wrappedValue: box) } get { state.wrappedValue.sharedReader } - nonmutating set { state.wrappedValue.sharedReader = newValue } } private let box: FetchBox @@ -45,7 +44,7 @@ public struct FetchOne: Sendable { /// /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader + public let sharedReader: SharedReader #endif /// A value associated with the underlying query. diff --git a/Sources/SQLiteData/Internal/FetchBox.swift b/Sources/SQLiteData/Internal/FetchBox.swift index e0d64073..992b79fa 100644 --- a/Sources/SQLiteData/Internal/FetchBox.swift +++ b/Sources/SQLiteData/Internal/FetchBox.swift @@ -1,32 +1,46 @@ #if canImport(SwiftUI) import Combine + import ConcurrencyExtras import Sharing import SwiftUI - final class FetchBox: @unchecked Sendable { - var sharedReader: SharedReader - var fetchKeyID: FetchKeyID? - private var swiftUICancellable: AnyCancellable? + final class FetchBox: Sendable { + let sharedReader: SharedReader + private let storage = LockIsolated(Storage()) + + var fetchKeyID: FetchKeyID? { + get { storage.withValue { $0.fetchKeyID } } + set { storage.withValue { $0.fetchKeyID = newValue } } + } init(sharedReader: SharedReader) { self.sharedReader = sharedReader } func update(from other: FetchBox) { - guard - let otherFetchKeyID = other.fetchKeyID, - otherFetchKeyID != fetchKeyID - else { return } - sharedReader = other.sharedReader - fetchKeyID = other.fetchKeyID + guard let otherFetchKeyID = other.fetchKeyID else { return } + let isAdopted = storage.withValue { + guard otherFetchKeyID != $0.fetchKeyID else { return false } + $0.fetchKeyID = otherFetchKeyID + return true + } + guard isAdopted else { return } + sharedReader.projectedValue = other.sharedReader.projectedValue } func subscribe(generation: SwiftUI.State) { guard #unavailable(iOS 17, macOS 14, tvOS 17, watchOS 10) else { return } _ = generation.wrappedValue - swiftUICancellable = sharedReader.publisher - .dropFirst() - .sink { _ in generation.wrappedValue &+= 1 } + storage.withValue { + $0.swiftUICancellable = sharedReader.publisher + .dropFirst() + .sink { _ in generation.wrappedValue &+= 1 } + } + } + + private struct Storage { + var fetchKeyID: FetchKeyID? + var swiftUICancellable: AnyCancellable? } } #endif diff --git a/Tests/SQLiteDataTests/FetchBoxTests.swift b/Tests/SQLiteDataTests/FetchBoxTests.swift index c4016cfd..a0a598c7 100644 --- a/Tests/SQLiteDataTests/FetchBoxTests.swift +++ b/Tests/SQLiteDataTests/FetchBoxTests.swift @@ -58,7 +58,7 @@ @Test func keylessReinitializationAfterLocalLoadIsIgnored() { let persisted = FetchBox(sharedReader: SharedReader(value: [Int]())) - persisted.sharedReader = SharedReader(value: [1, 2, 3]) + persisted.sharedReader.projectedValue = SharedReader(value: [1, 2, 3]).projectedValue let fresh = FetchBox(sharedReader: SharedReader(value: [Int]())) persisted.update(from: fresh) #expect(persisted.sharedReader.wrappedValue == [1, 2, 3]) From cae6f4d392967b8762f746e57167f35389e45e48 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Aug 2026 12:33:18 -0700 Subject: [PATCH 05/11] New integration target --- Examples/Examples.xcodeproj/project.pbxproj | 270 +++++++++++++++--- .../xcshareddata/swiftpm/Package.resolved | 20 +- .../xcschemes/CloudKitDemo.xcscheme | 3 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 +++ .../Integration/Assets.xcassets/Contents.json | 6 + Examples/Integration/IntegrationApp.swift | 9 + .../ChildIdentityReset.swift | 0 .../ParentDrivenQuery.swift | 0 .../ParentRerenderAnimations.swift | 0 .../ParentRerenderCancellation.swift | 0 .../ParentRerenderDynamicQuery.swift | 0 .../ParentRerenderLoadError.swift | 0 .../ParentRerenderLoadedData.swift | 0 14 files changed, 313 insertions(+), 41 deletions(-) create mode 100644 Examples/Integration/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Examples/Integration/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Examples/Integration/Assets.xcassets/Contents.json create mode 100644 Examples/Integration/IntegrationApp.swift rename Examples/{CaseStudies => Integration}/Regression Coverage/ChildIdentityReset.swift (100%) rename Examples/{CaseStudies => Integration}/Regression Coverage/ParentDrivenQuery.swift (100%) rename Examples/{CaseStudies => Integration}/Regression Coverage/ParentRerenderAnimations.swift (100%) rename Examples/{CaseStudies => Integration}/Regression Coverage/ParentRerenderCancellation.swift (100%) rename Examples/{CaseStudies => Integration}/Regression Coverage/ParentRerenderDynamicQuery.swift (100%) rename Examples/{CaseStudies => Integration}/Regression Coverage/ParentRerenderLoadError.swift (100%) rename Examples/{CaseStudies => Integration}/Regression Coverage/ParentRerenderLoadedData.swift (100%) diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 4d22126d..40b6bd82 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -56,6 +56,8 @@ CAF836982D4735620047AEB5 /* CaseStudies.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CaseStudies.app; sourceTree = BUILT_PRODUCTS_DIR; }; CAF836A82D4735640047AEB5 /* CaseStudiesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CaseStudiesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CAF836D82D4735AB0047AEB5 /* Reminders.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reminders.app; sourceTree = BUILT_PRODUCTS_DIR; }; + DC1584A630363595009DD95C /* Integration.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Integration.app; sourceTree = BUILT_PRODUCTS_DIR; }; + DC55FADE30363BB000F2D8D3 /* sqlite-data */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "sqlite-data"; path = "/Users/stephen/Developer/pointfreeco/sqlite-data"; sourceTree = ""; }; DCBE89CC2D483FB90071F499 /* SyncUps.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SyncUps.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -75,6 +77,14 @@ ); target = CAF836972D4735620047AEB5 /* CaseStudies */; }; + DC15854C303636A7009DD95C /* Exceptions for "CaseStudies" folder in "Integration" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Internal/CaseStudy.swift, + "Internal/Text+Template.swift", + ); + target = DC1584A530363595009DD95C /* Integration */; + }; DCA44CFA2D5D9D1E008D4E76 /* Exceptions for "Reminders" folder in "Reminders" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -116,6 +126,7 @@ isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( CAD4819A2D584B510004799A /* Exceptions for "CaseStudies" folder in "CaseStudies" target */, + DC15854C303636A7009DD95C /* Exceptions for "CaseStudies" folder in "Integration" target */, ); path = CaseStudies; sourceTree = ""; @@ -133,6 +144,11 @@ path = Reminders; sourceTree = ""; }; + DC1584A730363595009DD95C /* Integration */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Integration; + sourceTree = ""; + }; DCBE89CD2D483FB90071F499 /* SyncUps */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( @@ -146,52 +162,73 @@ /* Begin PBXFrameworksBuildPhase section */ CA2BDD9A2E71C30B000974D3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; files = ( CA2BDE2A2E71C469000974D3 /* SQLiteData in Frameworks */, ); + runOnlyForDeploymentPostprocessing = 0; }; CA5E46932DEBFE410069E0F8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; files = ( CA5E47092DECEFC80069E0F8 /* SnapshotTestingCustomDump in Frameworks */, CA5E47072DECEF0F0069E0F8 /* InlineSnapshotTesting in Frameworks */, CA5E470B2DECF0280069E0F8 /* DependenciesTestSupport in Frameworks */, ); + runOnlyForDeploymentPostprocessing = 0; }; CAD0017A2D874E6F00FA977A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; files = ( CAD001872D874F1F00FA977A /* DependenciesTestSupport in Frameworks */, ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836952D4735620047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; files = ( CA2BDE302E71C480000974D3 /* SQLiteData in Frameworks */, CA2908C92D4AF70E003F165F /* UIKitNavigation in Frameworks */, ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836A52D4735640047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836D52D4735AB0047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; files = ( CA2BDE2E2E71C479000974D3 /* SQLiteData in Frameworks */, CA14DBC92DA884C400E36852 /* CasePaths in Frameworks */, CA5E46912DEBB8570069E0F8 /* SwiftUINavigation in Frameworks */, ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC1584A330363595009DD95C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; }; DCBE89C92D483FB90071F499 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; files = ( DCF267392D48437300B680BE /* SwiftUINavigation in Frameworks */, DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */, CA2BDE2C2E71C472000974D3 /* SQLiteData in Frameworks */, DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */, ); + runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ @@ -207,6 +244,7 @@ DCBE89CD2D483FB90071F499 /* SyncUps */, CAD0017E2D874E6F00FA977A /* SyncUpTests */, CA2BDD9E2E71C30B000974D3 /* CloudKitDemo */, + DC1584A730363595009DD95C /* Integration */, CAF837022D4735C00047AEB5 /* Frameworks */, CAF836992D4735620047AEB5 /* Products */, ); @@ -222,6 +260,7 @@ CAD0017D2D874E6F00FA977A /* SyncUpTests.xctest */, CA5E46962DEBFE410069E0F8 /* RemindersTests.xctest */, CA2BDD9D2E71C30B000974D3 /* CloudKitDemo.app */, + DC1584A630363595009DD95C /* Integration.app */, ); name = Products; sourceTree = ""; @@ -229,6 +268,7 @@ CAF837022D4735C00047AEB5 /* Frameworks */ = { isa = PBXGroup; children = ( + DC55FADE30363BB000F2D8D3 /* sqlite-data */, CA2BDE272E71C42B000974D3 /* sqlite-data */, ); name = Frameworks; @@ -247,6 +287,8 @@ ); buildRules = ( ); + dependencies = ( + ); fileSystemSynchronizedGroups = ( CA2BDD9E2E71C30B000974D3 /* CloudKitDemo */, ); @@ -318,6 +360,8 @@ ); buildRules = ( ); + dependencies = ( + ); fileSystemSynchronizedGroups = ( CAF8369A2D4735620047AEB5 /* CaseStudies */, ); @@ -347,6 +391,8 @@ CAF836AB2D4735640047AEB5 /* CaseStudiesTests */, ); name = CaseStudiesTests; + packageProductDependencies = ( + ); productName = ExamplesTests; productReference = CAF836A82D4735640047AEB5 /* CaseStudiesTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -361,6 +407,8 @@ ); buildRules = ( ); + dependencies = ( + ); fileSystemSynchronizedGroups = ( CAF836D92D4735AB0047AEB5 /* Reminders */, ); @@ -374,6 +422,28 @@ productReference = CAF836D82D4735AB0047AEB5 /* Reminders.app */; productType = "com.apple.product-type.application"; }; + DC1584A530363595009DD95C /* Integration */ = { + isa = PBXNativeTarget; + buildConfigurationList = DC1584AE30363596009DD95C /* Build configuration list for PBXNativeTarget "Integration" */; + buildPhases = ( + DC1584A230363595009DD95C /* Sources */, + DC1584A330363595009DD95C /* Frameworks */, + DC1584A430363595009DD95C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + DC1584A730363595009DD95C /* Integration */, + ); + name = Integration; + packageProductDependencies = ( + ); + productName = Integration; + productReference = DC1584A630363595009DD95C /* Integration.app */; + productType = "com.apple.product-type.application"; + }; DCBE89CB2D483FB90071F499 /* SyncUps */ = { isa = PBXNativeTarget; buildConfigurationList = DCBE89F32D483FBA0071F499 /* Build configuration list for PBXNativeTarget "SyncUps" */; @@ -384,6 +454,8 @@ ); buildRules = ( ); + dependencies = ( + ); fileSystemSynchronizedGroups = ( DCBE89CD2D483FB90071F499 /* SyncUps */, ); @@ -405,7 +477,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1640; + LastSwiftUpdateCheck = 2700; LastUpgradeCheck = 2610; TargetAttributes = { CA2BDD9C2E71C30B000974D3 = { @@ -429,6 +501,9 @@ CAF836D72D4735AB0047AEB5 = { CreatedOnToolsVersion = 16.2; }; + DC1584A530363595009DD95C = { + CreatedOnToolsVersion = 27.0; + }; DCBE89CB2D483FB90071F499 = { CreatedOnToolsVersion = 16.2; }; @@ -462,6 +537,7 @@ DCBE89CB2D483FB90071F499 /* SyncUps */, CAD0017C2D874E6F00FA977A /* SyncUpTests */, CA2BDD9C2E71C30B000974D3 /* CloudKitDemo */, + DC1584A530363595009DD95C /* Integration */, ); }; /* End PBXProject section */ @@ -469,76 +545,118 @@ /* Begin PBXResourcesBuildPhase section */ CA2BDD9B2E71C30B000974D3 /* Resources */ = { isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CA5E46942DEBFE410069E0F8 /* Resources */ = { isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAD0017B2D874E6F00FA977A /* Resources */ = { isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836962D4735620047AEB5 /* Resources */ = { isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836A62D4735640047AEB5 /* Resources */ = { isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836D62D4735AB0047AEB5 /* Resources */ = { isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC1584A430363595009DD95C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; }; DCBE89CA2D483FB90071F499 /* Resources */ = { isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ CA2BDD992E71C30B000974D3 /* Sources */ = { isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CA5E46922DEBFE410069E0F8 /* Sources */ = { isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAD001792D874E6F00FA977A /* Sources */ = { isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836942D4735620047AEB5 /* Sources */ = { isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836A42D4735640047AEB5 /* Sources */ = { isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; CAF836D42D4735AB0047AEB5 /* Sources */ = { isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC1584A230363595009DD95C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; }; DCBE89C82D483FB90071F499 /* Sources */ = { isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; files = ( ); + runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ @@ -561,7 +679,7 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - CA2BDDA52E71C30D000974D3 /* Debug configuration for PBXNativeTarget "CloudKitDemo" */ = { + CA2BDDA52E71C30D000974D3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -591,7 +709,7 @@ }; name = Debug; }; - CA2BDDA62E71C30D000974D3 /* Release configuration for PBXNativeTarget "CloudKitDemo" */ = { + CA2BDDA62E71C30D000974D3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -621,7 +739,7 @@ }; name = Release; }; - CA5E469D2DEBFE420069E0F8 /* Debug configuration for PBXNativeTarget "RemindersTests" */ = { + CA5E469D2DEBFE420069E0F8 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -638,7 +756,7 @@ }; name = Debug; }; - CA5E469E2DEBFE420069E0F8 /* Release configuration for PBXNativeTarget "RemindersTests" */ = { + CA5E469E2DEBFE420069E0F8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -655,7 +773,7 @@ }; name = Release; }; - CAD001832D874E6F00FA977A /* Debug configuration for PBXNativeTarget "SyncUpTests" */ = { + CAD001832D874E6F00FA977A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -672,7 +790,7 @@ }; name = Debug; }; - CAD001842D874E6F00FA977A /* Release configuration for PBXNativeTarget "SyncUpTests" */ = { + CAD001842D874E6F00FA977A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -689,7 +807,7 @@ }; name = Release; }; - CAF836BA2D4735640047AEB5 /* Debug configuration for PBXProject "Examples" */ = { + CAF836BA2D4735640047AEB5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -756,7 +874,7 @@ }; name = Debug; }; - CAF836BB2D4735640047AEB5 /* Release configuration for PBXProject "Examples" */ = { + CAF836BB2D4735640047AEB5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -816,7 +934,7 @@ }; name = Release; }; - CAF836BD2D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudies" */ = { + CAF836BD2D4735640047AEB5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -844,7 +962,7 @@ }; name = Debug; }; - CAF836BE2D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudies" */ = { + CAF836BE2D4735640047AEB5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -872,7 +990,7 @@ }; name = Release; }; - CAF836C02D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudiesTests" */ = { + CAF836C02D4735640047AEB5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -888,7 +1006,7 @@ }; name = Debug; }; - CAF836C12D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudiesTests" */ = { + CAF836C12D4735640047AEB5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -904,7 +1022,7 @@ }; name = Release; }; - CAF836FA2D4735AD0047AEB5 /* Debug configuration for PBXNativeTarget "Reminders" */ = { + CAF836FA2D4735AD0047AEB5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -934,7 +1052,7 @@ }; name = Debug; }; - CAF836FB2D4735AD0047AEB5 /* Release configuration for PBXNativeTarget "Reminders" */ = { + CAF836FB2D4735AD0047AEB5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -964,7 +1082,69 @@ }; name = Release; }; - DCBE89ED2D483FBA0071F499 /* Debug configuration for PBXNativeTarget "SyncUps" */ = { + DC1584AF30363596009DD95C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Integration; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DC1584B030363596009DD95C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.Integration; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + DCBE89ED2D483FBA0071F499 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -995,7 +1175,7 @@ }; name = Debug; }; - DCBE89EE2D483FBA0071F499 /* Release configuration for PBXNativeTarget "SyncUps" */ = { + DCBE89EE2D483FBA0071F499 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1032,65 +1212,82 @@ CA2BDDA72E71C30D000974D3 /* Build configuration list for PBXNativeTarget "CloudKitDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( - CA2BDDA52E71C30D000974D3 /* Debug configuration for PBXNativeTarget "CloudKitDemo" */, - CA2BDDA62E71C30D000974D3 /* Release configuration for PBXNativeTarget "CloudKitDemo" */, + CA2BDDA52E71C30D000974D3 /* Debug */, + CA2BDDA62E71C30D000974D3 /* Release */, ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CA5E469C2DEBFE420069E0F8 /* Build configuration list for PBXNativeTarget "RemindersTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - CA5E469D2DEBFE420069E0F8 /* Debug configuration for PBXNativeTarget "RemindersTests" */, - CA5E469E2DEBFE420069E0F8 /* Release configuration for PBXNativeTarget "RemindersTests" */, + CA5E469D2DEBFE420069E0F8 /* Debug */, + CA5E469E2DEBFE420069E0F8 /* Release */, ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAD001852D874E6F00FA977A /* Build configuration list for PBXNativeTarget "SyncUpTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAD001832D874E6F00FA977A /* Debug configuration for PBXNativeTarget "SyncUpTests" */, - CAD001842D874E6F00FA977A /* Release configuration for PBXNativeTarget "SyncUpTests" */, + CAD001832D874E6F00FA977A /* Debug */, + CAD001842D874E6F00FA977A /* Release */, ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836932D4735620047AEB5 /* Build configuration list for PBXProject "Examples" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836BA2D4735640047AEB5 /* Debug configuration for PBXProject "Examples" */, - CAF836BB2D4735640047AEB5 /* Release configuration for PBXProject "Examples" */, + CAF836BA2D4735640047AEB5 /* Debug */, + CAF836BB2D4735640047AEB5 /* Release */, ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836BC2D4735640047AEB5 /* Build configuration list for PBXNativeTarget "CaseStudies" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836BD2D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudies" */, - CAF836BE2D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudies" */, + CAF836BD2D4735640047AEB5 /* Debug */, + CAF836BE2D4735640047AEB5 /* Release */, ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836BF2D4735640047AEB5 /* Build configuration list for PBXNativeTarget "CaseStudiesTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836C02D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudiesTests" */, - CAF836C12D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudiesTests" */, + CAF836C02D4735640047AEB5 /* Debug */, + CAF836C12D4735640047AEB5 /* Release */, ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836F92D4735AD0047AEB5 /* Build configuration list for PBXNativeTarget "Reminders" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836FA2D4735AD0047AEB5 /* Debug configuration for PBXNativeTarget "Reminders" */, - CAF836FB2D4735AD0047AEB5 /* Release configuration for PBXNativeTarget "Reminders" */, + CAF836FA2D4735AD0047AEB5 /* Debug */, + CAF836FB2D4735AD0047AEB5 /* Release */, ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC1584AE30363596009DD95C /* Build configuration list for PBXNativeTarget "Integration" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC1584AF30363596009DD95C /* Debug */, + DC1584B030363596009DD95C /* Release */, + ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DCBE89F32D483FBA0071F499 /* Build configuration list for PBXNativeTarget "SyncUps" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCBE89ED2D483FBA0071F499 /* Debug configuration for PBXNativeTarget "SyncUps" */, - DCBE89EE2D483FBA0071F499 /* Release configuration for PBXNativeTarget "SyncUps" */, + DCBE89ED2D483FBA0071F499 /* Debug */, + DCBE89EE2D483FBA0071F499 /* Release */, ); + defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ @@ -1100,8 +1297,6 @@ isa = XCLocalSwiftPackageReference; relativePath = ..; traits = ( - LazyInitializableByDefault, - StrictDecoding, ); }; /* End XCLocalSwiftPackageReference section */ @@ -1122,9 +1317,6 @@ kind = upToNextMajorVersion; minimumVersion = 1.7.0; }; - traits = ( - Clocks, - ); }; DCBE8A122D4842BF0071F499 /* XCRemoteSwiftPackageReference "swift-case-paths" */ = { isa = XCRemoteSwiftPackageReference; @@ -1141,8 +1333,6 @@ kind = upToNextMajorVersion; minimumVersion = 2.2.3; }; - traits = ( - ); }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8b742760..f8a07f26 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" : "c133bf7d10c8ce1e5d6506c3d2f080eac8b4c8c2827044d53a9b925e903564fd", + "originHash" : "dac4a8505ed5c78e26e317be3f0c9d46bbdd7964b22a357fba84a82dcc44b19a", "pins" : [ { "identity" : "combine-schedulers", @@ -73,6 +73,24 @@ "version" : "1.13.1" } }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "647c708be89f834fa6a6d4945442793a77ddf5b6", + "version" : "1.5.0" + } + }, + { + "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", diff --git a/Examples/Examples.xcodeproj/xcshareddata/xcschemes/CloudKitDemo.xcscheme b/Examples/Examples.xcodeproj/xcshareddata/xcschemes/CloudKitDemo.xcscheme index fbfd2df7..3753f201 100644 --- a/Examples/Examples.xcodeproj/xcshareddata/xcschemes/CloudKitDemo.xcscheme +++ b/Examples/Examples.xcodeproj/xcshareddata/xcschemes/CloudKitDemo.xcscheme @@ -17,6 +17,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "CA2BDD9C2E71C30B000974D3" BuildableName = "CloudKitDemo.app" + BlueprintName = "CloudKitDemo" ReferencedContainer = "container:Examples.xcodeproj"> @@ -46,6 +47,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "CA2BDD9C2E71C30B000974D3" BuildableName = "CloudKitDemo.app" + BlueprintName = "CloudKitDemo" ReferencedContainer = "container:Examples.xcodeproj"> @@ -62,6 +64,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "CA2BDD9C2E71C30B000974D3" BuildableName = "CloudKitDemo.app" + BlueprintName = "CloudKitDemo" ReferencedContainer = "container:Examples.xcodeproj"> diff --git a/Examples/Integration/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/Integration/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/Examples/Integration/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Integration/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/Integration/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..23058801 --- /dev/null +++ b/Examples/Integration/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Integration/Assets.xcassets/Contents.json b/Examples/Integration/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Examples/Integration/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/Integration/IntegrationApp.swift b/Examples/Integration/IntegrationApp.swift new file mode 100644 index 00000000..abb52ec6 --- /dev/null +++ b/Examples/Integration/IntegrationApp.swift @@ -0,0 +1,9 @@ +import SwiftUI + +@main +struct IntegrationApp: App { + var body: some Scene { + WindowGroup { + } + } +} diff --git a/Examples/CaseStudies/Regression Coverage/ChildIdentityReset.swift b/Examples/Integration/Regression Coverage/ChildIdentityReset.swift similarity index 100% rename from Examples/CaseStudies/Regression Coverage/ChildIdentityReset.swift rename to Examples/Integration/Regression Coverage/ChildIdentityReset.swift diff --git a/Examples/CaseStudies/Regression Coverage/ParentDrivenQuery.swift b/Examples/Integration/Regression Coverage/ParentDrivenQuery.swift similarity index 100% rename from Examples/CaseStudies/Regression Coverage/ParentDrivenQuery.swift rename to Examples/Integration/Regression Coverage/ParentDrivenQuery.swift diff --git a/Examples/CaseStudies/Regression Coverage/ParentRerenderAnimations.swift b/Examples/Integration/Regression Coverage/ParentRerenderAnimations.swift similarity index 100% rename from Examples/CaseStudies/Regression Coverage/ParentRerenderAnimations.swift rename to Examples/Integration/Regression Coverage/ParentRerenderAnimations.swift diff --git a/Examples/CaseStudies/Regression Coverage/ParentRerenderCancellation.swift b/Examples/Integration/Regression Coverage/ParentRerenderCancellation.swift similarity index 100% rename from Examples/CaseStudies/Regression Coverage/ParentRerenderCancellation.swift rename to Examples/Integration/Regression Coverage/ParentRerenderCancellation.swift diff --git a/Examples/CaseStudies/Regression Coverage/ParentRerenderDynamicQuery.swift b/Examples/Integration/Regression Coverage/ParentRerenderDynamicQuery.swift similarity index 100% rename from Examples/CaseStudies/Regression Coverage/ParentRerenderDynamicQuery.swift rename to Examples/Integration/Regression Coverage/ParentRerenderDynamicQuery.swift diff --git a/Examples/CaseStudies/Regression Coverage/ParentRerenderLoadError.swift b/Examples/Integration/Regression Coverage/ParentRerenderLoadError.swift similarity index 100% rename from Examples/CaseStudies/Regression Coverage/ParentRerenderLoadError.swift rename to Examples/Integration/Regression Coverage/ParentRerenderLoadError.swift diff --git a/Examples/CaseStudies/Regression Coverage/ParentRerenderLoadedData.swift b/Examples/Integration/Regression Coverage/ParentRerenderLoadedData.swift similarity index 100% rename from Examples/CaseStudies/Regression Coverage/ParentRerenderLoadedData.swift rename to Examples/Integration/Regression Coverage/ParentRerenderLoadedData.swift From f19620511a4b613e786983963d39eee3ea8f1a81 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Aug 2026 12:38:56 -0700 Subject: [PATCH 06/11] wip --- Examples/Examples.xcodeproj/project.pbxproj | 190 ++++++------------ .../xcshareddata/swiftpm/Package.resolved | 4 +- 2 files changed, 60 insertions(+), 134 deletions(-) diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 40b6bd82..a981aa5a 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -10,15 +10,14 @@ CA14DBC92DA884C400E36852 /* CasePaths in Frameworks */ = {isa = PBXBuildFile; productRef = CA14DBC82DA884C400E36852 /* CasePaths */; }; CA2908C92D4AF70E003F165F /* UIKitNavigation in Frameworks */ = {isa = PBXBuildFile; productRef = CA2908C82D4AF70E003F165F /* UIKitNavigation */; }; CA2BDE2A2E71C469000974D3 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CA2BDE292E71C469000974D3 /* SQLiteData */; }; - CA2BDE2C2E71C472000974D3 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CA2BDE2B2E71C472000974D3 /* SQLiteData */; }; - CA2BDE2E2E71C479000974D3 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CA2BDE2D2E71C479000974D3 /* SQLiteData */; }; - CA2BDE302E71C480000974D3 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CA2BDE2F2E71C480000974D3 /* SQLiteData */; }; CA5E46912DEBB8570069E0F8 /* SwiftUINavigation in Frameworks */ = {isa = PBXBuildFile; productRef = CA5E46902DEBB8570069E0F8 /* SwiftUINavigation */; }; CA5E47072DECEF0F0069E0F8 /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = CA5E47062DECEF0F0069E0F8 /* InlineSnapshotTesting */; }; CA5E47092DECEFC80069E0F8 /* SnapshotTestingCustomDump in Frameworks */ = {isa = PBXBuildFile; productRef = CA5E47082DECEFC80069E0F8 /* SnapshotTestingCustomDump */; }; CA5E470B2DECF0280069E0F8 /* DependenciesTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = CA5E470A2DECF0280069E0F8 /* DependenciesTestSupport */; }; CAD001872D874F1F00FA977A /* DependenciesTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = CAD001862D874F1F00FA977A /* DependenciesTestSupport */; }; DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */ = {isa = PBXBuildFile; productRef = DC5FA7472D4C63D60082743E /* DependenciesMacros */; }; + DCB8AD4A30363D3800ACF09E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = DCB8AD4930363D3800ACF09E /* SQLiteData */; }; + DCB8AD4C30363DB100ACF09E /* UIKitNavigation in Frameworks */ = {isa = PBXBuildFile; productRef = DCB8AD4B30363DB100ACF09E /* UIKitNavigation */; }; DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */ = {isa = PBXBuildFile; productRef = DCBE8A132D4842BF0071F499 /* CasePaths */; }; DCF267392D48437300B680BE /* SwiftUINavigation in Frameworks */ = {isa = PBXBuildFile; productRef = DCF267382D48437300B680BE /* SwiftUINavigation */; }; /* End PBXBuildFile section */ @@ -162,73 +161,56 @@ /* Begin PBXFrameworksBuildPhase section */ CA2BDD9A2E71C30B000974D3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( CA2BDE2A2E71C469000974D3 /* SQLiteData in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; CA5E46932DEBFE410069E0F8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( CA5E47092DECEFC80069E0F8 /* SnapshotTestingCustomDump in Frameworks */, CA5E47072DECEF0F0069E0F8 /* InlineSnapshotTesting in Frameworks */, CA5E470B2DECF0280069E0F8 /* DependenciesTestSupport in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; CAD0017A2D874E6F00FA977A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( CAD001872D874F1F00FA977A /* DependenciesTestSupport in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836952D4735620047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( - CA2BDE302E71C480000974D3 /* SQLiteData in Frameworks */, CA2908C92D4AF70E003F165F /* UIKitNavigation in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836A52D4735640047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836D52D4735AB0047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( - CA2BDE2E2E71C479000974D3 /* SQLiteData in Frameworks */, CA14DBC92DA884C400E36852 /* CasePaths in Frameworks */, CA5E46912DEBB8570069E0F8 /* SwiftUINavigation in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; DC1584A330363595009DD95C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( + DCB8AD4A30363D3800ACF09E /* SQLiteData in Frameworks */, + DCB8AD4C30363DB100ACF09E /* UIKitNavigation in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; DCBE89C92D483FB90071F499 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( DCF267392D48437300B680BE /* SwiftUINavigation in Frameworks */, DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */, - CA2BDE2C2E71C472000974D3 /* SQLiteData in Frameworks */, DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ @@ -287,8 +269,6 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( CA2BDD9E2E71C30B000974D3 /* CloudKitDemo */, ); @@ -360,15 +340,12 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( CAF8369A2D4735620047AEB5 /* CaseStudies */, ); name = CaseStudies; packageProductDependencies = ( CA2908C82D4AF70E003F165F /* UIKitNavigation */, - CA2BDE2F2E71C480000974D3 /* SQLiteData */, ); productName = Examples; productReference = CAF836982D4735620047AEB5 /* CaseStudies.app */; @@ -391,8 +368,6 @@ CAF836AB2D4735640047AEB5 /* CaseStudiesTests */, ); name = CaseStudiesTests; - packageProductDependencies = ( - ); productName = ExamplesTests; productReference = CAF836A82D4735640047AEB5 /* CaseStudiesTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -407,8 +382,6 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( CAF836D92D4735AB0047AEB5 /* Reminders */, ); @@ -416,7 +389,6 @@ packageProductDependencies = ( CA14DBC82DA884C400E36852 /* CasePaths */, CA5E46902DEBB8570069E0F8 /* SwiftUINavigation */, - CA2BDE2D2E71C479000974D3 /* SQLiteData */, ); productName = Reminders; productReference = CAF836D82D4735AB0047AEB5 /* Reminders.app */; @@ -432,13 +404,13 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( DC1584A730363595009DD95C /* Integration */, ); name = Integration; packageProductDependencies = ( + DCB8AD4930363D3800ACF09E /* SQLiteData */, + DCB8AD4B30363DB100ACF09E /* UIKitNavigation */, ); productName = Integration; productReference = DC1584A630363595009DD95C /* Integration.app */; @@ -454,8 +426,6 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( DCBE89CD2D483FB90071F499 /* SyncUps */, ); @@ -464,7 +434,6 @@ DCBE8A132D4842BF0071F499 /* CasePaths */, DCF267382D48437300B680BE /* SwiftUINavigation */, DC5FA7472D4C63D60082743E /* DependenciesMacros */, - CA2BDE2B2E71C472000974D3 /* SQLiteData */, ); productName = SyncUps; productReference = DCBE89CC2D483FB90071F499 /* SyncUps.app */; @@ -523,7 +492,7 @@ DCF267372D48437300B680BE /* XCRemoteSwiftPackageReference "swift-navigation" */, DC5FA7462D4C63D60082743E /* XCRemoteSwiftPackageReference "swift-dependencies" */, CA5E47052DECEF0F0069E0F8 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, - CA2BDE282E71C469000974D3 /* XCLocalSwiftPackageReference ".." */, + DCB8AD4830363D3800ACF09E /* XCLocalSwiftPackageReference "../../sqlite-data" */, ); preferredProjectObjectVersion = 77; productRefGroup = CAF836992D4735620047AEB5 /* Products */; @@ -545,118 +514,86 @@ /* Begin PBXResourcesBuildPhase section */ CA2BDD9B2E71C30B000974D3 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CA5E46942DEBFE410069E0F8 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAD0017B2D874E6F00FA977A /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836962D4735620047AEB5 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836A62D4735640047AEB5 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836D62D4735AB0047AEB5 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; DC1584A430363595009DD95C /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; DCBE89CA2D483FB90071F499 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ CA2BDD992E71C30B000974D3 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CA5E46922DEBFE410069E0F8 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAD001792D874E6F00FA977A /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836942D4735620047AEB5 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836A42D4735640047AEB5 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; CAF836D42D4735AB0047AEB5 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; DC1584A230363595009DD95C /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; DCBE89C82D483FB90071F499 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ @@ -679,7 +616,7 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - CA2BDDA52E71C30D000974D3 /* Debug */ = { + CA2BDDA52E71C30D000974D3 /* Debug configuration for PBXNativeTarget "CloudKitDemo" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -709,7 +646,7 @@ }; name = Debug; }; - CA2BDDA62E71C30D000974D3 /* Release */ = { + CA2BDDA62E71C30D000974D3 /* Release configuration for PBXNativeTarget "CloudKitDemo" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -739,7 +676,7 @@ }; name = Release; }; - CA5E469D2DEBFE420069E0F8 /* Debug */ = { + CA5E469D2DEBFE420069E0F8 /* Debug configuration for PBXNativeTarget "RemindersTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -756,7 +693,7 @@ }; name = Debug; }; - CA5E469E2DEBFE420069E0F8 /* Release */ = { + CA5E469E2DEBFE420069E0F8 /* Release configuration for PBXNativeTarget "RemindersTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -773,7 +710,7 @@ }; name = Release; }; - CAD001832D874E6F00FA977A /* Debug */ = { + CAD001832D874E6F00FA977A /* Debug configuration for PBXNativeTarget "SyncUpTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -790,7 +727,7 @@ }; name = Debug; }; - CAD001842D874E6F00FA977A /* Release */ = { + CAD001842D874E6F00FA977A /* Release configuration for PBXNativeTarget "SyncUpTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -807,7 +744,7 @@ }; name = Release; }; - CAF836BA2D4735640047AEB5 /* Debug */ = { + CAF836BA2D4735640047AEB5 /* Debug configuration for PBXProject "Examples" */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -874,7 +811,7 @@ }; name = Debug; }; - CAF836BB2D4735640047AEB5 /* Release */ = { + CAF836BB2D4735640047AEB5 /* Release configuration for PBXProject "Examples" */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -934,7 +871,7 @@ }; name = Release; }; - CAF836BD2D4735640047AEB5 /* Debug */ = { + CAF836BD2D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudies" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -962,7 +899,7 @@ }; name = Debug; }; - CAF836BE2D4735640047AEB5 /* Release */ = { + CAF836BE2D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudies" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -990,7 +927,7 @@ }; name = Release; }; - CAF836C02D4735640047AEB5 /* Debug */ = { + CAF836C02D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudiesTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -1006,7 +943,7 @@ }; name = Debug; }; - CAF836C12D4735640047AEB5 /* Release */ = { + CAF836C12D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudiesTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -1022,7 +959,7 @@ }; name = Release; }; - CAF836FA2D4735AD0047AEB5 /* Debug */ = { + CAF836FA2D4735AD0047AEB5 /* Debug configuration for PBXNativeTarget "Reminders" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1052,7 +989,7 @@ }; name = Debug; }; - CAF836FB2D4735AD0047AEB5 /* Release */ = { + CAF836FB2D4735AD0047AEB5 /* Release configuration for PBXNativeTarget "Reminders" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1082,7 +1019,7 @@ }; name = Release; }; - DC1584AF30363596009DD95C /* Debug */ = { + DC1584AF30363596009DD95C /* Debug configuration for PBXNativeTarget "Integration" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1113,7 +1050,7 @@ }; name = Debug; }; - DC1584B030363596009DD95C /* Release */ = { + DC1584B030363596009DD95C /* Release configuration for PBXNativeTarget "Integration" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1144,7 +1081,7 @@ }; name = Release; }; - DCBE89ED2D483FBA0071F499 /* Debug */ = { + DCBE89ED2D483FBA0071F499 /* Debug configuration for PBXNativeTarget "SyncUps" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1175,7 +1112,7 @@ }; name = Debug; }; - DCBE89EE2D483FBA0071F499 /* Release */ = { + DCBE89EE2D483FBA0071F499 /* Release configuration for PBXNativeTarget "SyncUps" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1212,91 +1149,86 @@ CA2BDDA72E71C30D000974D3 /* Build configuration list for PBXNativeTarget "CloudKitDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( - CA2BDDA52E71C30D000974D3 /* Debug */, - CA2BDDA62E71C30D000974D3 /* Release */, + CA2BDDA52E71C30D000974D3 /* Debug configuration for PBXNativeTarget "CloudKitDemo" */, + CA2BDDA62E71C30D000974D3 /* Release configuration for PBXNativeTarget "CloudKitDemo" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CA5E469C2DEBFE420069E0F8 /* Build configuration list for PBXNativeTarget "RemindersTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - CA5E469D2DEBFE420069E0F8 /* Debug */, - CA5E469E2DEBFE420069E0F8 /* Release */, + CA5E469D2DEBFE420069E0F8 /* Debug configuration for PBXNativeTarget "RemindersTests" */, + CA5E469E2DEBFE420069E0F8 /* Release configuration for PBXNativeTarget "RemindersTests" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAD001852D874E6F00FA977A /* Build configuration list for PBXNativeTarget "SyncUpTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAD001832D874E6F00FA977A /* Debug */, - CAD001842D874E6F00FA977A /* Release */, + CAD001832D874E6F00FA977A /* Debug configuration for PBXNativeTarget "SyncUpTests" */, + CAD001842D874E6F00FA977A /* Release configuration for PBXNativeTarget "SyncUpTests" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836932D4735620047AEB5 /* Build configuration list for PBXProject "Examples" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836BA2D4735640047AEB5 /* Debug */, - CAF836BB2D4735640047AEB5 /* Release */, + CAF836BA2D4735640047AEB5 /* Debug configuration for PBXProject "Examples" */, + CAF836BB2D4735640047AEB5 /* Release configuration for PBXProject "Examples" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836BC2D4735640047AEB5 /* Build configuration list for PBXNativeTarget "CaseStudies" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836BD2D4735640047AEB5 /* Debug */, - CAF836BE2D4735640047AEB5 /* Release */, + CAF836BD2D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudies" */, + CAF836BE2D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudies" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836BF2D4735640047AEB5 /* Build configuration list for PBXNativeTarget "CaseStudiesTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836C02D4735640047AEB5 /* Debug */, - CAF836C12D4735640047AEB5 /* Release */, + CAF836C02D4735640047AEB5 /* Debug configuration for PBXNativeTarget "CaseStudiesTests" */, + CAF836C12D4735640047AEB5 /* Release configuration for PBXNativeTarget "CaseStudiesTests" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CAF836F92D4735AD0047AEB5 /* Build configuration list for PBXNativeTarget "Reminders" */ = { isa = XCConfigurationList; buildConfigurations = ( - CAF836FA2D4735AD0047AEB5 /* Debug */, - CAF836FB2D4735AD0047AEB5 /* Release */, + CAF836FA2D4735AD0047AEB5 /* Debug configuration for PBXNativeTarget "Reminders" */, + CAF836FB2D4735AD0047AEB5 /* Release configuration for PBXNativeTarget "Reminders" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DC1584AE30363596009DD95C /* Build configuration list for PBXNativeTarget "Integration" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC1584AF30363596009DD95C /* Debug */, - DC1584B030363596009DD95C /* Release */, + DC1584AF30363596009DD95C /* Debug configuration for PBXNativeTarget "Integration" */, + DC1584B030363596009DD95C /* Release configuration for PBXNativeTarget "Integration" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DCBE89F32D483FBA0071F499 /* Build configuration list for PBXNativeTarget "SyncUps" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCBE89ED2D483FBA0071F499 /* Debug */, - DCBE89EE2D483FBA0071F499 /* Release */, + DCBE89ED2D483FBA0071F499 /* Debug configuration for PBXNativeTarget "SyncUps" */, + DCBE89EE2D483FBA0071F499 /* Release configuration for PBXNativeTarget "SyncUps" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - CA2BDE282E71C469000974D3 /* XCLocalSwiftPackageReference ".." */ = { + DCB8AD4830363D3800ACF09E /* XCLocalSwiftPackageReference "../../sqlite-data" */ = { isa = XCLocalSwiftPackageReference; - relativePath = ..; + relativePath = "../../sqlite-data"; traits = ( + CasePaths, + ColumnCoding, + LazyInitializableByDefault, + StrictDecoding, ); }; /* End XCLocalSwiftPackageReference section */ @@ -1351,21 +1283,6 @@ isa = XCSwiftPackageProductDependency; productName = SQLiteData; }; - CA2BDE2B2E71C472000974D3 /* SQLiteData */ = { - isa = XCSwiftPackageProductDependency; - package = CA2BDE282E71C469000974D3 /* XCLocalSwiftPackageReference ".." */; - productName = SQLiteData; - }; - CA2BDE2D2E71C479000974D3 /* SQLiteData */ = { - isa = XCSwiftPackageProductDependency; - package = CA2BDE282E71C469000974D3 /* XCLocalSwiftPackageReference ".." */; - productName = SQLiteData; - }; - CA2BDE2F2E71C480000974D3 /* SQLiteData */ = { - isa = XCSwiftPackageProductDependency; - package = CA2BDE282E71C469000974D3 /* XCLocalSwiftPackageReference ".." */; - productName = SQLiteData; - }; CA5E46902DEBB8570069E0F8 /* SwiftUINavigation */ = { isa = XCSwiftPackageProductDependency; package = DCF267372D48437300B680BE /* XCRemoteSwiftPackageReference "swift-navigation" */; @@ -1396,6 +1313,15 @@ package = DC5FA7462D4C63D60082743E /* XCRemoteSwiftPackageReference "swift-dependencies" */; productName = DependenciesMacros; }; + DCB8AD4930363D3800ACF09E /* SQLiteData */ = { + isa = XCSwiftPackageProductDependency; + productName = SQLiteData; + }; + DCB8AD4B30363DB100ACF09E /* UIKitNavigation */ = { + isa = XCSwiftPackageProductDependency; + package = DCF267372D48437300B680BE /* XCRemoteSwiftPackageReference "swift-navigation" */; + productName = UIKitNavigation; + }; DCBE8A132D4842BF0071F499 /* CasePaths */ = { isa = XCSwiftPackageProductDependency; package = DCBE8A122D4842BF0071F499 /* XCRemoteSwiftPackageReference "swift-case-paths" */; diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f8a07f26..8b1e616a 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "206cbce3882b4de9aee19ce62ac5b7306cadd45b", - "version" : "1.7.3" + "revision" : "794f4b0a9cf32042592388d014f6a1ea987d323a", + "version" : "1.9.1" } }, { From 5a2e2daf31d39a8975d91dd72312e38b788c4d5c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Aug 2026 12:45:00 -0700 Subject: [PATCH 07/11] fix --- Examples/Examples.xcodeproj/project.pbxproj | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index a981aa5a..1c1c6257 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -18,6 +18,9 @@ DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */ = {isa = PBXBuildFile; productRef = DC5FA7472D4C63D60082743E /* DependenciesMacros */; }; DCB8AD4A30363D3800ACF09E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = DCB8AD4930363D3800ACF09E /* SQLiteData */; }; DCB8AD4C30363DB100ACF09E /* UIKitNavigation in Frameworks */ = {isa = PBXBuildFile; productRef = DCB8AD4B30363DB100ACF09E /* UIKitNavigation */; }; + DCB8AD4E30363F2100ACF09E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = DCB8AD4D30363F2100ACF09E /* SQLiteData */; }; + DCB8AD5030363F2600ACF09E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = DCB8AD4F30363F2600ACF09E /* SQLiteData */; }; + DCB8AD5230363F2A00ACF09E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = DCB8AD5130363F2A00ACF09E /* SQLiteData */; }; DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */ = {isa = PBXBuildFile; productRef = DCBE8A132D4842BF0071F499 /* CasePaths */; }; DCF267392D48437300B680BE /* SwiftUINavigation in Frameworks */ = {isa = PBXBuildFile; productRef = DCF267382D48437300B680BE /* SwiftUINavigation */; }; /* End PBXBuildFile section */ @@ -182,6 +185,7 @@ CAF836952D4735620047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( + DCB8AD4E30363F2100ACF09E /* SQLiteData in Frameworks */, CA2908C92D4AF70E003F165F /* UIKitNavigation in Frameworks */, ); }; @@ -193,6 +197,7 @@ CAF836D52D4735AB0047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( + DCB8AD5030363F2600ACF09E /* SQLiteData in Frameworks */, CA14DBC92DA884C400E36852 /* CasePaths in Frameworks */, CA5E46912DEBB8570069E0F8 /* SwiftUINavigation in Frameworks */, ); @@ -209,6 +214,7 @@ files = ( DCF267392D48437300B680BE /* SwiftUINavigation in Frameworks */, DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */, + DCB8AD5230363F2A00ACF09E /* SQLiteData in Frameworks */, DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */, ); }; @@ -346,6 +352,7 @@ name = CaseStudies; packageProductDependencies = ( CA2908C82D4AF70E003F165F /* UIKitNavigation */, + DCB8AD4D30363F2100ACF09E /* SQLiteData */, ); productName = Examples; productReference = CAF836982D4735620047AEB5 /* CaseStudies.app */; @@ -389,6 +396,7 @@ packageProductDependencies = ( CA14DBC82DA884C400E36852 /* CasePaths */, CA5E46902DEBB8570069E0F8 /* SwiftUINavigation */, + DCB8AD4F30363F2600ACF09E /* SQLiteData */, ); productName = Reminders; productReference = CAF836D82D4735AB0047AEB5 /* Reminders.app */; @@ -434,6 +442,7 @@ DCBE8A132D4842BF0071F499 /* CasePaths */, DCF267382D48437300B680BE /* SwiftUINavigation */, DC5FA7472D4C63D60082743E /* DependenciesMacros */, + DCB8AD5130363F2A00ACF09E /* SQLiteData */, ); productName = SyncUps; productReference = DCBE89CC2D483FB90071F499 /* SyncUps.app */; @@ -1322,6 +1331,18 @@ package = DCF267372D48437300B680BE /* XCRemoteSwiftPackageReference "swift-navigation" */; productName = UIKitNavigation; }; + DCB8AD4D30363F2100ACF09E /* SQLiteData */ = { + isa = XCSwiftPackageProductDependency; + productName = SQLiteData; + }; + DCB8AD4F30363F2600ACF09E /* SQLiteData */ = { + isa = XCSwiftPackageProductDependency; + productName = SQLiteData; + }; + DCB8AD5130363F2A00ACF09E /* SQLiteData */ = { + isa = XCSwiftPackageProductDependency; + productName = SQLiteData; + }; DCBE8A132D4842BF0071F499 /* CasePaths */ = { isa = XCSwiftPackageProductDependency; package = DCBE8A122D4842BF0071F499 /* XCRemoteSwiftPackageReference "swift-case-paths" */; From b6f9dfa4a7002783cb7dcbb7e861fbc8c3973a7c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Aug 2026 12:45:08 -0700 Subject: [PATCH 08/11] wip --- .../xcschemes/Integration.xcscheme | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme diff --git a/Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme b/Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme new file mode 100644 index 00000000..45608be5 --- /dev/null +++ b/Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9af9f3fbd8bdfc59920ae889b3efda5f293d7cb3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Aug 2026 12:47:15 -0700 Subject: [PATCH 09/11] cleanup --- Examples/Reminders/ReminderForm.swift | 2 +- Examples/SyncUps/SyncUpDetail.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/Reminders/ReminderForm.swift b/Examples/Reminders/ReminderForm.swift index a41e28e0..a1566249 100644 --- a/Examples/Reminders/ReminderForm.swift +++ b/Examples/Reminders/ReminderForm.swift @@ -14,7 +14,7 @@ struct ReminderFormView: View { @Environment(\.dismiss) var dismiss init(reminder: Reminder.Draft, remindersList: RemindersList) { - _remindersList = FetchOne(wrappedValue: remindersList, RemindersList.find(remindersList.id)) + _remindersList = FetchOne(wrappedValue: remindersList) self.reminder = reminder } diff --git a/Examples/SyncUps/SyncUpDetail.swift b/Examples/SyncUps/SyncUpDetail.swift index 9818ff3e..212f88cf 100644 --- a/Examples/SyncUps/SyncUpDetail.swift +++ b/Examples/SyncUps/SyncUpDetail.swift @@ -37,7 +37,7 @@ final class SyncUpDetailModel: HashableObject { self.destination = destination _attendees = FetchAll(Attendee.where { $0.syncUpID.eq(syncUp.id) }) _meetings = FetchAll(Meeting.where { $0.syncUpID.eq(syncUp.id) }) - _syncUp = FetchOne(wrappedValue: syncUp, SyncUp.find(syncUp.id)) + _syncUp = FetchOne(wrappedValue: syncUp) } func deleteMeetings(atOffsets indices: IndexSet) { From f4ad7ed7bebbe7a30565a7cb1f5dd313eec62135 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 19 Aug 2026 14:57:12 -0500 Subject: [PATCH 10/11] Add some prints to make it clear queries are not being executed. --- .../xcshareddata/swiftpm/Package.resolved | 20 +------------------ .../ParentDrivenQuery.swift | 9 ++++++++- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8b1e616a..8bdb0fcc 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" : "dac4a8505ed5c78e26e317be3f0c9d46bbdd7964b22a357fba84a82dcc44b19a", + "originHash" : "c56e7b70de4fe8bcc798354797a8405c0eeb2e9bc68bc0f202c14a8b11a0a97f", "pins" : [ { "identity" : "combine-schedulers", @@ -73,24 +73,6 @@ "version" : "1.13.1" } }, - { - "identity" : "swift-docc-plugin", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-docc-plugin", - "state" : { - "revision" : "647c708be89f834fa6a6d4945442793a77ddf5b6", - "version" : "1.5.0" - } - }, - { - "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", diff --git a/Examples/Integration/Regression Coverage/ParentDrivenQuery.swift b/Examples/Integration/Regression Coverage/ParentDrivenQuery.swift index b0a4d337..e6b4b855 100644 --- a/Examples/Integration/Regression Coverage/ParentDrivenQuery.swift +++ b/Examples/Integration/Regression Coverage/ParentDrivenQuery.swift @@ -33,6 +33,7 @@ private struct FactsListView: View { @FetchAll private var facts: [Fact] init(isFavoritesOnly: Bool) { + print("FactsListView.init") if isFavoritesOnly { _facts = FetchAll(Fact.where(\.isFavorite)) } else { @@ -65,7 +66,13 @@ nonisolated private struct Fact: Identifiable { extension DatabaseWriter where Self == DatabaseQueue { static var parentDrivenQueryDatabase: Self { - let databaseQueue = try! DatabaseQueue() + var configuration = Configuration() + configuration.prepareDatabase { + $0.trace { + print($0.description) + } + } + let databaseQueue = try! DatabaseQueue(configuration: configuration) var migrator = DatabaseMigrator() migrator.registerMigration("Create 'facts' table") { db in try #sql( From 804d512671896acd0ca6c9a37bc335d375cb4e08 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Aug 2026 13:03:03 -0700 Subject: [PATCH 11/11] wip --- Examples/Examples.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 1c1c6257..2c6f2d6c 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 100; + objectVersion = 77; objects = { /* Begin PBXBuildFile section */