From 6cfcb20fb230a292f94937d3872d08131780abcb Mon Sep 17 00:00:00 2001 From: Axel Ancona Esselmann Date: Thu, 29 Feb 2024 11:54:23 -0800 Subject: [PATCH] Initial commit --- .gitignore | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + Package.swift | 24 + .../CoreDataCreationError.swift | 8 + .../EntityDescriptionAttribute.swift | 91 ++ .../NSAttributeDescription+Extensions.swift | 13 + .../NSEntityDescription+Extensions.swift | 999 ++++++++++++++++++ .../NSManagedObjectModel+Extensions.swift | 54 + ...rsistentCloudKitContainer+Extensions.swift | 128 +++ .../NSPersistentContainer+Extensions.swift | 77 ++ .../SelfDescribingCoreDataEntity.swift | 8 + .../URL+Extensions_.swift | 35 + .../ProgrammaticCoreDataTests.swift | 12 + 13 files changed, 1465 insertions(+) create mode 100644 .gitignore create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Package.swift create mode 100644 Sources/ProgrammaticCoreData/CoreDataCreationError.swift create mode 100644 Sources/ProgrammaticCoreData/EntityDescriptionAttribute.swift create mode 100644 Sources/ProgrammaticCoreData/NSAttributeDescription+Extensions.swift create mode 100644 Sources/ProgrammaticCoreData/NSEntityDescription+Extensions.swift create mode 100644 Sources/ProgrammaticCoreData/NSManagedObjectModel+Extensions.swift create mode 100644 Sources/ProgrammaticCoreData/NSPersistentCloudKitContainer+Extensions.swift create mode 100644 Sources/ProgrammaticCoreData/NSPersistentContainer+Extensions.swift create mode 100644 Sources/ProgrammaticCoreData/SelfDescribingCoreDataEntity.swift create mode 100644 Sources/ProgrammaticCoreData/URL+Extensions_.swift create mode 100644 Tests/ProgrammaticCoreDataTests/ProgrammaticCoreDataTests.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..3207fe1 --- /dev/null +++ b/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "ProgrammaticCoreData", + platforms: [ + .macOS(.v14), + .iOS(.v17) + ], + products: [ + .library( + name: "ProgrammaticCoreData", + targets: ["ProgrammaticCoreData"]), + ], + targets: [ + .target( + name: "ProgrammaticCoreData"), + .testTarget( + name: "ProgrammaticCoreDataTests", + dependencies: ["ProgrammaticCoreData"]), + ] +) diff --git a/Sources/ProgrammaticCoreData/CoreDataCreationError.swift b/Sources/ProgrammaticCoreData/CoreDataCreationError.swift new file mode 100644 index 0000000..10c32a9 --- /dev/null +++ b/Sources/ProgrammaticCoreData/CoreDataCreationError.swift @@ -0,0 +1,8 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import Foundation + +public enum CoreDataCreationError: Error { + case storeDescriptionMissing +} diff --git a/Sources/ProgrammaticCoreData/EntityDescriptionAttribute.swift b/Sources/ProgrammaticCoreData/EntityDescriptionAttribute.swift new file mode 100644 index 0000000..5ed1c55 --- /dev/null +++ b/Sources/ProgrammaticCoreData/EntityDescriptionAttribute.swift @@ -0,0 +1,91 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import CoreData + +public enum EntityDescriptionAttribute { + case undefined(KeyPath) + case integer16(KeyPath) + case integer32(KeyPath) + case integer64(KeyPath) + case decimal(KeyPath) + case double(KeyPath) + case float(KeyPath) + case string(KeyPath) + case boolean(KeyPath) + case date(KeyPath) + case binaryData(KeyPath) + case uuid(KeyPath) + case uri(KeyPath) + case transformable(KeyPath) + case objectID(KeyPath) + case composite(KeyPath) + + public var attributeType: NSAttributeType { + switch self { + case .undefined: + return .undefinedAttributeType + case .integer16: + return .integer16AttributeType + case .integer32: + return .integer32AttributeType + case .integer64: + return .integer64AttributeType + case .decimal: + return .decimalAttributeType + case .double: + return .doubleAttributeType + case .float: + return .floatAttributeType + case .string: + return .stringAttributeType + case .boolean: + return .booleanAttributeType + case .date: + return .dateAttributeType + case .binaryData: + return .binaryDataAttributeType + case .uuid: + return .UUIDAttributeType + case .uri: + return .URIAttributeType + case .transformable: + return .transformableAttributeType + case .objectID: + return .objectIDAttributeType + case .composite: + return .compositeAttributeType + } + } + + public var keyPath: KeyPath { + switch self { + case + .undefined(let keyPath), + .integer16(let keyPath), + .integer32(let keyPath), + .integer64(let keyPath), + .decimal(let keyPath), + .double(let keyPath), + .float(let keyPath), + .string(let keyPath), + .boolean(let keyPath), + .date(let keyPath), + .binaryData(let keyPath), + .uuid(let keyPath), + .uri(let keyPath), + .transformable(let keyPath), + .objectID(let keyPath), + .composite(let keyPath): + return keyPath + } + } + + public var nsAttributeType: NSAttributeDescription { + NSAttributeDescription( + name: NSExpression(forKeyPath: keyPath).keyPath, + type: attributeType, + defaultValue: nil + ) + } +} diff --git a/Sources/ProgrammaticCoreData/NSAttributeDescription+Extensions.swift b/Sources/ProgrammaticCoreData/NSAttributeDescription+Extensions.swift new file mode 100644 index 0000000..aa45166 --- /dev/null +++ b/Sources/ProgrammaticCoreData/NSAttributeDescription+Extensions.swift @@ -0,0 +1,13 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import CoreData + +public extension NSAttributeDescription { + convenience init(name: String, type: NSAttributeType, defaultValue: Any? = nil) { + self.init() + self.name = name + self.attributeType = type + self.defaultValue = defaultValue + } +} diff --git a/Sources/ProgrammaticCoreData/NSEntityDescription+Extensions.swift b/Sources/ProgrammaticCoreData/NSEntityDescription+Extensions.swift new file mode 100644 index 0000000..10abcea --- /dev/null +++ b/Sources/ProgrammaticCoreData/NSEntityDescription+Extensions.swift @@ -0,0 +1,999 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import CoreData + +public extension NSEntityDescription { + + convenience init(_ type: EntityType.Type) + where EntityType: AnyObject + { + self.init() + let name = NSStringFromClass(type) + self.name = name + self.managedObjectClassName = name + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init(type, properties: [a0.nsAttributeType]) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute, + _ a18: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType, + a18.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute, + _ a18: EntityDescriptionAttribute, + _ a19: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType, + a18.nsAttributeType, + a19.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute, + _ a18: EntityDescriptionAttribute, + _ a19: EntityDescriptionAttribute, + _ a20: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType, + a18.nsAttributeType, + a19.nsAttributeType, + a20.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute, + _ a18: EntityDescriptionAttribute, + _ a19: EntityDescriptionAttribute, + _ a20: EntityDescriptionAttribute, + _ a21: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType, + a18.nsAttributeType, + a19.nsAttributeType, + a20.nsAttributeType, + a21.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute, + _ a18: EntityDescriptionAttribute, + _ a19: EntityDescriptionAttribute, + _ a20: EntityDescriptionAttribute, + _ a21: EntityDescriptionAttribute, + _ a22: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType, + a18.nsAttributeType, + a19.nsAttributeType, + a20.nsAttributeType, + a21.nsAttributeType, + a22.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute, + _ a18: EntityDescriptionAttribute, + _ a19: EntityDescriptionAttribute, + _ a20: EntityDescriptionAttribute, + _ a21: EntityDescriptionAttribute, + _ a22: EntityDescriptionAttribute, + _ a23: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType, + a18.nsAttributeType, + a19.nsAttributeType, + a20.nsAttributeType, + a21.nsAttributeType, + a22.nsAttributeType, + a23.nsAttributeType + ] + ) + } + + convenience init( + _ type: EntityType.Type, + properties + a0: EntityDescriptionAttribute, + _ a1: EntityDescriptionAttribute, + _ a2: EntityDescriptionAttribute, + _ a3: EntityDescriptionAttribute, + _ a4: EntityDescriptionAttribute, + _ a5: EntityDescriptionAttribute, + _ a6: EntityDescriptionAttribute, + _ a7: EntityDescriptionAttribute, + _ a8: EntityDescriptionAttribute, + _ a9: EntityDescriptionAttribute, + _ a10: EntityDescriptionAttribute, + _ a11: EntityDescriptionAttribute, + _ a12: EntityDescriptionAttribute, + _ a13: EntityDescriptionAttribute, + _ a14: EntityDescriptionAttribute, + _ a15: EntityDescriptionAttribute, + _ a16: EntityDescriptionAttribute, + _ a17: EntityDescriptionAttribute, + _ a18: EntityDescriptionAttribute, + _ a19: EntityDescriptionAttribute, + _ a20: EntityDescriptionAttribute, + _ a21: EntityDescriptionAttribute, + _ a22: EntityDescriptionAttribute, + _ a23: EntityDescriptionAttribute, + _ a24: EntityDescriptionAttribute + ) + where EntityType: AnyObject + { + self.init( + type, + properties: [ + a0.nsAttributeType, + a1.nsAttributeType, + a2.nsAttributeType, + a3.nsAttributeType, + a4.nsAttributeType, + a5.nsAttributeType, + a6.nsAttributeType, + a7.nsAttributeType, + a8.nsAttributeType, + a9.nsAttributeType, + a10.nsAttributeType, + a11.nsAttributeType, + a12.nsAttributeType, + a13.nsAttributeType, + a14.nsAttributeType, + a15.nsAttributeType, + a16.nsAttributeType, + a17.nsAttributeType, + a18.nsAttributeType, + a19.nsAttributeType, + a20.nsAttributeType, + a21.nsAttributeType, + a22.nsAttributeType, + a23.nsAttributeType, + a24.nsAttributeType + ] + ) + } + + private convenience init( + _ type: EntityType.Type, + properties : [NSAttributeDescription] + ) + where EntityType: AnyObject + { + self.init() + let name = NSStringFromClass(type) + self.name = name + self.managedObjectClassName = name + self.properties = properties + } +} diff --git a/Sources/ProgrammaticCoreData/NSManagedObjectModel+Extensions.swift b/Sources/ProgrammaticCoreData/NSManagedObjectModel+Extensions.swift new file mode 100644 index 0000000..31346ad --- /dev/null +++ b/Sources/ProgrammaticCoreData/NSManagedObjectModel+Extensions.swift @@ -0,0 +1,54 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import CoreData + +public extension NSManagedObjectModel { + + convenience init(_ entities: [any SelfDescribingCoreDataEntity.Type]) { + self.init() + self.entities = entities.map { $0.entityDescription } + } + + convenience init(_ entities: any SelfDescribingCoreDataEntity.Type...) { + self.init(entities) + } + + @discardableResult + func append(_ entities: [NSEntityDescription]) -> Self { + self.entities += entities + return self + } + + func createContainer( + name: String, + location: NSPersistentContainer.Location + ) async throws -> NSPersistentContainer { + try await NSPersistentContainer.create( + name: name, + model: self, + location: location + ) + } + + func createCloudContainer( + name: String, + cloudContainerIdentifier: String, + options: CloudOptions + ) throws -> NSPersistentContainer { + try NSPersistentCloudKitContainer( + name: name, + managedObjectModel: self, + cloudContainerIdentifier: cloudContainerIdentifier, + options: options + ) + } + + func createLocalContainer(name: String, path: URL) -> NSPersistentContainer { + NSPersistentContainer(name: name, managedObjectModel: self, path: path) + } + + func createLocalContainer(name: String, subdirectory: String?) throws -> NSPersistentContainer { + try NSPersistentContainer(name: name, managedObjectModel: self, subdirecotry: subdirectory) + } +} diff --git a/Sources/ProgrammaticCoreData/NSPersistentCloudKitContainer+Extensions.swift b/Sources/ProgrammaticCoreData/NSPersistentCloudKitContainer+Extensions.swift new file mode 100644 index 0000000..685ec86 --- /dev/null +++ b/Sources/ProgrammaticCoreData/NSPersistentCloudKitContainer+Extensions.swift @@ -0,0 +1,128 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import CoreData + +public extension NSPersistentCloudKitContainer { + convenience init( + name: String, + managedObjectModel: NSManagedObjectModel, + cloudContainerIdentifier: String, + options: CloudOptions? = nil + ) throws { + self.init(name: name, managedObjectModel: managedObjectModel) + guard let description = persistentStoreDescriptions.first else { + throw CoreDataCreationError.storeDescriptionMissing + } + if let options { + if let enablingPersistentHistoryTracking = options.enablingPersistentHistoryTracking { + let nsNumber = enablingPersistentHistoryTracking as NSNumber + description.setOption(nsNumber, forKey: NSPersistentHistoryTrackingKey) + description.setOption(nsNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) + } + } + let options = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudContainerIdentifier) + description.cloudKitContainerOptions = options + persistentStoreDescriptions = [ description ] + } +} + +import Combine + +@globalActor +public actor RemoteChangeManager { + + static public var shared = RemoteChangeManager() + + enum Error: Swift.Error { + case notInitialized + case invalidHistoryResults + } + + @RemoteChangeManager + var bag = Set() + + init() { } + + @RemoteChangeManager + public func initialize(container: NSPersistentContainer) { + NotificationCenter.default.publisher( + for: .NSPersistentStoreRemoteChange, + object: container.persistentStoreCoordinator + ) + .sink { _ in + Task { [weak self] in + do { + try self?.processRemoteChange() + } catch { + print(error) + } + } + } + .store(in: &bag) + _backgroundContext = container.newBackgroundContext() + } + + @RemoteChangeManager + private var _backgroundContext: NSManagedObjectContext? + + @RemoteChangeManager + private func backgroundContext() throws -> NSManagedObjectContext { + guard let context = _backgroundContext else { + throw Error.notInitialized + } + return context + } + + @RemoteChangeManager + private func processRemoteChange() throws { + let history = try fetchHistory() + guard !history.isEmpty else { + return + } + print("processRemoteChange - History: ", history.count) + try merge(history) + try clean() + print("done") + // 59557 values + } + + @RemoteChangeManager + func fetchHistory() throws -> [NSPersistentHistoryTransaction] { + let context = try backgroundContext() + let fromDate = Date.distantPast + let historyFetchRequest = NSPersistentHistoryChangeRequest + .fetchHistory(after: fromDate) + if let fetchRequest = NSPersistentHistoryTransaction.fetchRequest { + historyFetchRequest.fetchRequest = fetchRequest + } + guard + let historyResult = try context.execute(historyFetchRequest) as? NSPersistentHistoryResult, + let history = historyResult.result as? [NSPersistentHistoryTransaction] + else { + throw Error.invalidHistoryResults + } + return history + } + + @RemoteChangeManager + func merge(_ transactions: [NSPersistentHistoryTransaction]) throws { + let context = try backgroundContext() + context.perform { + for transaction in transactions { + context.mergeChanges(fromContextDidSave: transaction.objectIDNotification()) + } + } + } + + @RemoteChangeManager + func clean() throws { + let context = try backgroundContext() +// let sevenDaysAgo = Date.now + let sevenDaysAgo = Date.now.addingTimeInterval(-604_800) + let purgeHistoryRequest = + NSPersistentHistoryChangeRequest.deleteHistory( + before: sevenDaysAgo) + try context.execute(purgeHistoryRequest) + } +} diff --git a/Sources/ProgrammaticCoreData/NSPersistentContainer+Extensions.swift b/Sources/ProgrammaticCoreData/NSPersistentContainer+Extensions.swift new file mode 100644 index 0000000..aa994d8 --- /dev/null +++ b/Sources/ProgrammaticCoreData/NSPersistentContainer+Extensions.swift @@ -0,0 +1,77 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import CoreData + +public struct CloudOptions { + public var enablingPersistentHistoryTracking: Bool? = nil + + public init(enablingPersistentHistoryTracking: Bool? = true) { + self.enablingPersistentHistoryTracking = enablingPersistentHistoryTracking + } +} + +public extension NSPersistentContainer { + + enum Location { + public static func cloud(cloudContainerIdentifier: String) -> Self { + return .cloud(cloudContainerIdentifier: cloudContainerIdentifier, options: CloudOptions()) + } + + case cloud(cloudContainerIdentifier: String, options: CloudOptions) + case local(subdirecotry: String?) + + var isCloud: Bool { + switch self { + case .cloud: + return true + case .local: + return false + } + } + } + + convenience init(name: String, managedObjectModel: NSManagedObjectModel, path: URL) { + self.init(name: name, managedObjectModel: managedObjectModel) + persistentStoreDescriptions[0] = NSPersistentStoreDescription(url: path) + } + + + convenience init(name: String, managedObjectModel: NSManagedObjectModel, subdirecotry: String?) throws { + self.init(name: name, managedObjectModel: managedObjectModel) + let path = try URL.libraryDbPath(modelName: name, subdirectory: subdirecotry) + persistentStoreDescriptions[0] = NSPersistentStoreDescription(url: path) + } + + func loadPersistentStores() async throws { + try await withCheckedThrowingContinuation { [unowned self] (continuation: CheckedContinuation) in + self.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + }) + } + } + + static func create( + name: String, + model: NSManagedObjectModel, + location: Location + ) async throws -> NSPersistentContainer { + let container: NSPersistentContainer + switch location { + case .cloud(let cloudContainerIdentifier, let options): + container = try model.createCloudContainer( + name: name, + cloudContainerIdentifier: cloudContainerIdentifier, + options: options + ) + case .local(let subdirectory): + container = try model.createLocalContainer(name: name, subdirectory: subdirectory) + } + try await container.loadPersistentStores() + return container + } +} diff --git a/Sources/ProgrammaticCoreData/SelfDescribingCoreDataEntity.swift b/Sources/ProgrammaticCoreData/SelfDescribingCoreDataEntity.swift new file mode 100644 index 0000000..894a4dc --- /dev/null +++ b/Sources/ProgrammaticCoreData/SelfDescribingCoreDataEntity.swift @@ -0,0 +1,8 @@ +// Created by Axel Ancona Esselmann on 2/25/24. +// + +import CoreData + +public protocol SelfDescribingCoreDataEntity { + static var entityDescription: NSEntityDescription { get } +} diff --git a/Sources/ProgrammaticCoreData/URL+Extensions_.swift b/Sources/ProgrammaticCoreData/URL+Extensions_.swift new file mode 100644 index 0000000..41bb89e --- /dev/null +++ b/Sources/ProgrammaticCoreData/URL+Extensions_.swift @@ -0,0 +1,35 @@ +// Created by Axel Ancona Esselmann on 2/24/24. +// + +import Foundation + +public extension URL { + static func libraryDbPath(modelName: String, subdirectory: String?) throws -> URL { + let fileManager = FileManager.default + + let appName = Bundle.main.bundleIdentifier?.components(separatedBy: ".").last ?? "" + var dbDir = try fileManager.url( + for: .libraryDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: false + ) + .appendingPathComponent(appName) + .appendingPathComponent("db") + if let subdirectory = subdirectory { + dbDir = dbDir.appendingPathComponent(subdirectory) + } + dbDir = dbDir.appendingPathComponent(modelName) + + if !fileManager.fileExists(atPath: dbDir.relativePath) { + try fileManager.createDirectory( + at: dbDir, + withIntermediateDirectories: true, + attributes: nil + ) + } + + return dbDir + .appendingPathComponent(modelName + ".sqlite") + } +} diff --git a/Tests/ProgrammaticCoreDataTests/ProgrammaticCoreDataTests.swift b/Tests/ProgrammaticCoreDataTests/ProgrammaticCoreDataTests.swift new file mode 100644 index 0000000..66d3a63 --- /dev/null +++ b/Tests/ProgrammaticCoreDataTests/ProgrammaticCoreDataTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import ProgrammaticCoreData + +final class ProgrammaticCoreDataTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +}