From a294e013dee017a59a4c6a78eed49dc376f952d5 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Wed, 16 Oct 2024 16:40:21 -0400 Subject: [PATCH] last commit before PR --- .../Databases/Database+ModelContext.swift | 4 - .../Databases/Database+Queryable.swift | 66 +++---- .../EnvironmentValues+Database.swift | 2 +- .../DataThespian/Databases/QueryError.swift | 10 +- .../Databases/Queryable+Extensions.swift | 170 +++++++++--------- .../DataThespian/Databases/Queryable.swift | 36 ++-- Sources/DataThespian/Databases/Selector.swift | 60 ++++--- .../Databases/UniqueKeyPath.swift | 5 +- .../SwiftData/ModelContext+Queryable.swift | 86 ++++----- .../SwiftData/PersistentIdentifier.swift | 100 ++++++----- 10 files changed, 275 insertions(+), 264 deletions(-) diff --git a/Sources/DataThespian/Databases/Database+ModelContext.swift b/Sources/DataThespian/Databases/Database+ModelContext.swift index 90faeb6..8735e58 100644 --- a/Sources/DataThespian/Databases/Database+ModelContext.swift +++ b/Sources/DataThespian/Databases/Database+ModelContext.swift @@ -42,10 +42,6 @@ try await self.withModelContext { try $0.delete(where: predicate) } } - // public func insert(_ closuer: @Sendable @escaping () -> some PersistentModel) async - // -> PersistentIdentifier - // { await self.withModelContext { $0.insert(closuer) } } - public func fetch( _ selectDescriptor: @escaping @Sendable () -> FetchDescriptor, with closure: @escaping @Sendable ([T]) throws -> U diff --git a/Sources/DataThespian/Databases/Database+Queryable.swift b/Sources/DataThespian/Databases/Database+Queryable.swift index f887682..403e585 100644 --- a/Sources/DataThespian/Databases/Database+Queryable.swift +++ b/Sources/DataThespian/Databases/Database+Queryable.swift @@ -27,45 +27,47 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public import SwiftData +#if canImport(SwiftData) + public import SwiftData -extension Database { - public func save() async throws { - try await self.withModelContext { try $0.save() } - } + extension Database { + public func save() async throws { + try await self.withModelContext { try $0.save() } + } - public func insert( - _ closuer: @Sendable @escaping () -> PersistentModelType, - with closure: @escaping @Sendable (PersistentModelType) throws -> U - ) async rethrows -> U { - try await self.withModelContext { - try $0.insert(closuer, with: closure) + public func insert( + _ closuer: @Sendable @escaping () -> PersistentModelType, + with closure: @escaping @Sendable (PersistentModelType) throws -> U + ) async rethrows -> U { + try await self.withModelContext { + try $0.insert(closuer, with: closure) + } } - } - public func getOptional( - for selector: Selector.Get, - with closure: @escaping @Sendable (PersistentModelType?) throws -> U - ) async rethrows -> U { - try await self.withModelContext { - try $0.getOptional(for: selector, with: closure) + public func getOptional( + for selector: Selector.Get, + with closure: @escaping @Sendable (PersistentModelType?) throws -> U + ) async rethrows -> U { + try await self.withModelContext { + try $0.getOptional(for: selector, with: closure) + } } - } - public func fetch( - for selector: Selector.List, - with closure: @escaping @Sendable ([PersistentModelType]) throws -> U - ) async rethrows -> U { - try await self.withModelContext { - try $0.fetch(for: selector, with: closure) + public func fetch( + for selector: Selector.List, + with closure: @escaping @Sendable ([PersistentModelType]) throws -> U + ) async rethrows -> U { + try await self.withModelContext { + try $0.fetch(for: selector, with: closure) + } } - } - public func delete(_ selector: Selector.Delete) - async throws - { - try await self.withModelContext { - try $0.delete(selector) + public func delete(_ selector: Selector.Delete) + async throws + { + try await self.withModelContext { + try $0.delete(selector) + } } } -} +#endif diff --git a/Sources/DataThespian/Databases/EnvironmentValues+Database.swift b/Sources/DataThespian/Databases/EnvironmentValues+Database.swift index b2ed50a..966ef15 100644 --- a/Sources/DataThespian/Databases/EnvironmentValues+Database.swift +++ b/Sources/DataThespian/Databases/EnvironmentValues+Database.swift @@ -27,7 +27,7 @@ // OTHER DEALINGS IN THE SOFTWARE. // -#if canImport(SwiftUI) +#if canImport(SwiftUI) && canImport(SwiftData) import Foundation import SwiftData public import SwiftUI diff --git a/Sources/DataThespian/Databases/QueryError.swift b/Sources/DataThespian/Databases/QueryError.swift index b2ef23d..7b31182 100644 --- a/Sources/DataThespian/Databases/QueryError.swift +++ b/Sources/DataThespian/Databases/QueryError.swift @@ -27,8 +27,10 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public import SwiftData +#if canImport(SwiftData) + public import SwiftData -public enum QueryError: Error { - case itemNotFound(Selector.Get) -} + public enum QueryError: Error { + case itemNotFound(Selector.Get) + } +#endif diff --git a/Sources/DataThespian/Databases/Queryable+Extensions.swift b/Sources/DataThespian/Databases/Queryable+Extensions.swift index 91d97fa..4594bc6 100644 --- a/Sources/DataThespian/Databases/Queryable+Extensions.swift +++ b/Sources/DataThespian/Databases/Queryable+Extensions.swift @@ -27,109 +27,111 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public import SwiftData +#if canImport(SwiftData) + public import SwiftData -extension Queryable { - @discardableResult - public func insert( - _ closuer: @Sendable @escaping () -> PersistentModelType - ) async -> Model { - await self.insert(closuer, with: Model.init) - } + extension Queryable { + @discardableResult + public func insert( + _ closuer: @Sendable @escaping () -> PersistentModelType + ) async -> Model { + await self.insert(closuer, with: Model.init) + } - public func getOptional( - for selector: Selector.Get - ) async -> Model? { - await self.getOptional(for: selector) { persistentModel in - persistentModel.flatMap(Model.init) + public func getOptional( + for selector: Selector.Get + ) async -> Model? { + await self.getOptional(for: selector) { persistentModel in + persistentModel.flatMap(Model.init) + } } - } - public func fetch( - for selector: Selector.List - ) async -> [Model] { - await self.fetch(for: selector) { persistentModels in - persistentModels.map(Model.init) + public func fetch( + for selector: Selector.List + ) async -> [Model] { + await self.fetch(for: selector) { persistentModels in + persistentModels.map(Model.init) + } } - } - public func get( - for selector: Selector.Get - ) async throws -> Model { - try await self.getOptional(for: selector) { persistentModel in - guard let persistentModel else { - throw QueryError.itemNotFound(selector) + public func get( + for selector: Selector.Get + ) async throws -> Model { + try await self.getOptional(for: selector) { persistentModel in + guard let persistentModel else { + throw QueryError.itemNotFound(selector) + } + return Model(persistentModel) } - return Model(persistentModel) } - } - public func get( - for selector: Selector.Get, - with closure: @escaping @Sendable (PersistentModelType) throws -> U - ) async throws -> U { - try await self.getOptional(for: selector) { persistentModel in - guard let persistentModel else { - throw QueryError.itemNotFound(selector) + public func get( + for selector: Selector.Get, + with closure: @escaping @Sendable (PersistentModelType) throws -> U + ) async throws -> U { + try await self.getOptional(for: selector) { persistentModel in + guard let persistentModel else { + throw QueryError.itemNotFound(selector) + } + return try closure(persistentModel) } - return try closure(persistentModel) } - } - public func update( - for selector: Selector.Get, - with closure: @escaping @Sendable (PersistentModelType) throws -> Void - ) async throws { - try await self.get(for: selector, with: closure) - } + public func update( + for selector: Selector.Get, + with closure: @escaping @Sendable (PersistentModelType) throws -> Void + ) async throws { + try await self.get(for: selector, with: closure) + } - public func update( - for selector: Selector.List, - with closure: @escaping @Sendable ([PersistentModelType]) throws -> Void - ) async throws { - try await self.fetch(for: selector, with: closure) - } + public func update( + for selector: Selector.List, + with closure: @escaping @Sendable ([PersistentModelType]) throws -> Void + ) async throws { + try await self.fetch(for: selector, with: closure) + } - public func insertIf( - _ model: @Sendable @escaping () -> PersistentModelType, - notExist selector: @Sendable @escaping (PersistentModelType) -> - Selector.Get - ) async -> Model { - let persistentModel = model() - let selector = selector(persistentModel) - let modelOptional = await self.getOptional(for: selector) + public func insertIf( + _ model: @Sendable @escaping () -> PersistentModelType, + notExist selector: @Sendable @escaping (PersistentModelType) -> + Selector.Get + ) async -> Model { + let persistentModel = model() + let selector = selector(persistentModel) + let modelOptional = await self.getOptional(for: selector) - if let modelOptional { - return modelOptional - } else { - return await self.insert(model) + if let modelOptional { + return modelOptional + } else { + return await self.insert(model) + } } - } - public func insertIf( - _ model: @Sendable @escaping () -> PersistentModelType, - notExist selector: @Sendable @escaping (PersistentModelType) -> - Selector.Get, - with closure: @escaping @Sendable (PersistentModelType) throws -> U - ) async throws -> U { - let model = await self.insertIf(model, notExist: selector) - return try await self.get(for: .model(model), with: closure) + public func insertIf( + _ model: @Sendable @escaping () -> PersistentModelType, + notExist selector: @Sendable @escaping (PersistentModelType) -> + Selector.Get, + with closure: @escaping @Sendable (PersistentModelType) throws -> U + ) async throws -> U { + let model = await self.insertIf(model, notExist: selector) + return try await self.get(for: .model(model), with: closure) + } } -} -extension Queryable { - public func deleteModels(_ models: [Model]) async throws - { - try await withThrowingTaskGroup( - of: Void.self, - body: { group in - for model in models { - group.addTask { - try await self.delete(.model(model)) + extension Queryable { + public func deleteModels(_ models: [Model]) async throws + { + try await withThrowingTaskGroup( + of: Void.self, + body: { group in + for model in models { + group.addTask { + try await self.delete(.model(model)) + } } + try await group.waitForAll() } - try await group.waitForAll() - } - ) + ) + } } -} +#endif diff --git a/Sources/DataThespian/Databases/Queryable.swift b/Sources/DataThespian/Databases/Queryable.swift index dc3b697..8bb0de9 100644 --- a/Sources/DataThespian/Databases/Queryable.swift +++ b/Sources/DataThespian/Databases/Queryable.swift @@ -27,25 +27,27 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public import SwiftData +#if canImport(SwiftData) + public import SwiftData -public protocol Queryable: Sendable { - func save() async throws + public protocol Queryable: Sendable { + func save() async throws - func insert( - _ closuer: @Sendable @escaping () -> PersistentModelType, - with closure: @escaping @Sendable (PersistentModelType) throws -> U - ) async rethrows -> U + func insert( + _ closuer: @Sendable @escaping () -> PersistentModelType, + with closure: @escaping @Sendable (PersistentModelType) throws -> U + ) async rethrows -> U - func getOptional( - for selector: Selector.Get, - with closure: @escaping @Sendable (PersistentModelType?) throws -> U - ) async rethrows -> U + func getOptional( + for selector: Selector.Get, + with closure: @escaping @Sendable (PersistentModelType?) throws -> U + ) async rethrows -> U - func fetch( - for selector: Selector.List, - with closure: @escaping @Sendable ([PersistentModelType]) throws -> U - ) async rethrows -> U + func fetch( + for selector: Selector.List, + with closure: @escaping @Sendable ([PersistentModelType]) throws -> U + ) async rethrows -> U - func delete(_ selector: Selector.Delete) async throws -} + func delete(_ selector: Selector.Delete) async throws + } +#endif diff --git a/Sources/DataThespian/Databases/Selector.swift b/Sources/DataThespian/Databases/Selector.swift index ce9f491..6b2e559 100644 --- a/Sources/DataThespian/Databases/Selector.swift +++ b/Sources/DataThespian/Databases/Selector.swift @@ -27,38 +27,40 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public import Foundation -public import SwiftData +#if canImport(SwiftData) + public import Foundation + public import SwiftData -public enum Selector: Sendable { - public enum Delete: Sendable { - case predicate(Predicate) - case all - case model(Model) + public enum Selector: Sendable { + public enum Delete: Sendable { + case predicate(Predicate) + case all + case model(Model) + } + public enum List: Sendable { + case descriptor(FetchDescriptor) + } + public enum Get: Sendable { + case model(Model) + case predicate(Predicate) + } } - public enum List: Sendable { - case descriptor(FetchDescriptor) - } - public enum Get: Sendable { - case model(Model) - case predicate(Predicate) - } -} -extension Selector.Get { - @available(*, unavailable, message: "Not implemented yet.") - public static func unique( - _ key: UniqueKeyableType, - equals value: UniqueKeyableType.ValueType - ) -> Self where UniqueKeyableType.Model == T { - .predicate( - key.predicate(equals: value) - ) + extension Selector.Get { + @available(*, unavailable, message: "Not implemented yet.") + public static func unique( + _ key: UniqueKeyableType, + equals value: UniqueKeyableType.ValueType + ) -> Self where UniqueKeyableType.Model == T { + .predicate( + key.predicate(equals: value) + ) + } } -} -extension Selector.List { - public static func all() -> Selector.List { - .descriptor(.init()) + extension Selector.List { + public static func all() -> Selector.List { + .descriptor(.init()) + } } -} +#endif diff --git a/Sources/DataThespian/Databases/UniqueKeyPath.swift b/Sources/DataThespian/Databases/UniqueKeyPath.swift index a1c1034..9c065ca 100644 --- a/Sources/DataThespian/Databases/UniqueKeyPath.swift +++ b/Sources/DataThespian/Databases/UniqueKeyPath.swift @@ -30,12 +30,13 @@ public import Foundation public struct UniqueKeyPath: UniqueKey { + private let keyPath: KeyPath & Sendable + internal init(keyPath: any KeyPath & Sendable) { self.keyPath = keyPath } - private let keyPath: KeyPath & Sendable - + // swiftlint:disable:next unavailable_function public func predicate(equals value: ValueType) -> Predicate { fatalError("Not implemented yet.") } diff --git a/Sources/DataThespian/SwiftData/ModelContext+Queryable.swift b/Sources/DataThespian/SwiftData/ModelContext+Queryable.swift index 1f1a2bd..934e8b5 100644 --- a/Sources/DataThespian/SwiftData/ModelContext+Queryable.swift +++ b/Sources/DataThespian/SwiftData/ModelContext+Queryable.swift @@ -27,54 +27,56 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public import SwiftData +#if canImport(SwiftData) + public import SwiftData -extension ModelContext { - public func insert( - _ closuer: @Sendable @escaping () -> PersistentModelType, - with closure: @escaping @Sendable (PersistentModelType) throws -> U - ) rethrows -> U { - let persistentModel = closuer() - self.insert(persistentModel) - return try closure(persistentModel) - } + extension ModelContext { + public func insert( + _ closuer: @Sendable @escaping () -> PersistentModelType, + with closure: @escaping @Sendable (PersistentModelType) throws -> U + ) rethrows -> U { + let persistentModel = closuer() + self.insert(persistentModel) + return try closure(persistentModel) + } - public func getOptional( - for selector: Selector.Get, - with closure: @escaping @Sendable (PersistentModelType?) throws -> U - ) throws -> U { - let persistentModel: PersistentModelType? - switch selector { - case .model(let model): - persistentModel = try self.existingModel(for: model) - case .predicate(let predicate): - persistentModel = try self.first(where: predicate) + public func getOptional( + for selector: Selector.Get, + with closure: @escaping @Sendable (PersistentModelType?) throws -> U + ) throws -> U { + let persistentModel: PersistentModelType? + switch selector { + case .model(let model): + persistentModel = try self.existingModel(for: model) + case .predicate(let predicate): + persistentModel = try self.first(where: predicate) + } + return try closure(persistentModel) } - return try closure(persistentModel) - } - public func fetch( - for selector: Selector.List, - with closure: @escaping @Sendable ([PersistentModelType]) throws -> U - ) throws -> U { - let persistentModels: [PersistentModelType] - switch selector { - case .descriptor(let descriptor): - persistentModels = try self.fetch(descriptor) + public func fetch( + for selector: Selector.List, + with closure: @escaping @Sendable ([PersistentModelType]) throws -> U + ) throws -> U { + let persistentModels: [PersistentModelType] + switch selector { + case .descriptor(let descriptor): + persistentModels = try self.fetch(descriptor) + } + return try closure(persistentModels) } - return try closure(persistentModels) - } - public func delete(_ selector: Selector.Delete) throws { - switch selector { - case .all: - try self.delete(model: PersistentModelType.self) - case .model(let model): - if let persistentModel = try self.existingModel(for: model) { - self.delete(persistentModel) + public func delete(_ selector: Selector.Delete) throws { + switch selector { + case .all: + try self.delete(model: PersistentModelType.self) + case .model(let model): + if let persistentModel = try self.existingModel(for: model) { + self.delete(persistentModel) + } + case .predicate(let predicate): + try self.delete(model: PersistentModelType.self, where: predicate) } - case .predicate(let predicate): - try self.delete(model: PersistentModelType.self, where: predicate) } } -} +#endif diff --git a/Sources/DataThespian/SwiftData/PersistentIdentifier.swift b/Sources/DataThespian/SwiftData/PersistentIdentifier.swift index b0f39ba..dee0c99 100644 --- a/Sources/DataThespian/SwiftData/PersistentIdentifier.swift +++ b/Sources/DataThespian/SwiftData/PersistentIdentifier.swift @@ -27,62 +27,64 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import CoreData -import Foundation -import SwiftData +#if canImport(CoreData) && canImport(SwiftData) + import CoreData + import Foundation + import SwiftData -/// Returns the value of a child property of an object using reflection. -/// -/// - Parameters: -/// - object: The object to inspect. -/// - childName: The name of the child property to retrieve. -/// - Returns: The value of the child property, or nil if it does not exist. -private func getMirrorChildValue(of object: Any, childName: String) -> Any? { - guard let child = Mirror(reflecting: object).children.first(where: { $0.label == childName }) - else { - return nil - } - - return child.value -} - -// Extension to add computed properties for accessing underlying CoreData -// implementation details of PersistentIdentifier -extension PersistentIdentifier { - // Private stored property to hold reference to underlying implementation - private var mirrorImplementation: Any? { - guard let implementation = getMirrorChildValue(of: self, childName: "implementation") else { - assertionFailure("Should always be there.") + /// Returns the value of a child property of an object using reflection. + /// + /// - Parameters: + /// - object: The object to inspect. + /// - childName: The name of the child property to retrieve. + /// - Returns: The value of the child property, or nil if it does not exist. + private func getMirrorChildValue(of object: Any, childName: String) -> Any? { + guard let child = Mirror(reflecting: object).children.first(where: { $0.label == childName }) + else { return nil } - return implementation + + return child.value } - // Computed property to access managedObjectID from implementation - private var objectID: NSManagedObjectID? { - guard let mirrorImplementation, - let objectID = getMirrorChildValue(of: mirrorImplementation, childName: "managedObjectID") - as? NSManagedObjectID - else { - return nil + // Extension to add computed properties for accessing underlying CoreData + // implementation details of PersistentIdentifier + extension PersistentIdentifier { + // Private stored property to hold reference to underlying implementation + private var mirrorImplementation: Any? { + guard let implementation = getMirrorChildValue(of: self, childName: "implementation") else { + assertionFailure("Should always be there.") + return nil + } + return implementation } - return objectID - } - // Computed property to access uriRepresentation from objectID - private var uriRepresentation: URL? { - objectID?.uriRepresentation() - } + // Computed property to access managedObjectID from implementation + private var objectID: NSManagedObjectID? { + guard let mirrorImplementation, + let objectID = getMirrorChildValue(of: mirrorImplementation, childName: "managedObjectID") + as? NSManagedObjectID + else { + return nil + } + return objectID + } - // swiftlint:disable:next discouraged_optional_boolean - internal var isTemporary: Bool? { - guard let mirrorImplementation, - let isTemporary = getMirrorChildValue(of: mirrorImplementation, childName: "isTemporary") - as? Bool - else { - assertionFailure("Should always be there.") - return nil + // Computed property to access uriRepresentation from objectID + private var uriRepresentation: URL? { + objectID?.uriRepresentation() + } + + // swiftlint:disable:next discouraged_optional_boolean + internal var isTemporary: Bool? { + guard let mirrorImplementation, + let isTemporary = getMirrorChildValue(of: mirrorImplementation, childName: "isTemporary") + as? Bool + else { + assertionFailure("Should always be there.") + return nil + } + return isTemporary } - return isTemporary } -} +#endif